ISIS-903: TranslationService, LocaleProvider,  TrString class,

also:
- deprecated a bunch of old *Event files in the applib, for wrapper factory 
only.


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/67e234b8
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/67e234b8
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/67e234b8

Branch: refs/heads/master
Commit: 67e234b84230d62ab99ba935c288966ac6ada9dd
Parents: faf70c0
Author: Dan Haywood <[email protected]>
Authored: Fri Feb 13 16:51:44 2015 +0000
Committer: Dan Haywood <[email protected]>
Committed: Wed Feb 18 14:07:29 2015 +0000

----------------------------------------------------------------------
 .../apache/isis/applib/events/AccessEvent.java  |   5 +-
 .../isis/applib/events/ActionArgumentEvent.java |   5 +-
 .../applib/events/ActionInvocationEvent.java    |   5 +-
 .../applib/events/ActionUsabilityEvent.java     |   5 +-
 .../applib/events/ActionVisibilityEvent.java    |   5 +-
 .../applib/events/CollectionAccessEvent.java    |   5 +-
 .../applib/events/CollectionAddToEvent.java     |   5 +-
 .../applib/events/CollectionMethodEvent.java    |   6 +-
 .../events/CollectionRemoveFromEvent.java       |   5 +-
 .../applib/events/CollectionUsabilityEvent.java |   5 +-
 .../events/CollectionVisibilityEvent.java       |   5 +-
 .../isis/applib/events/InteractionEvent.java    |   5 +-
 .../isis/applib/events/ObjectTitleEvent.java    |   5 +-
 .../isis/applib/events/ObjectValidityEvent.java |   5 +-
 .../applib/events/ObjectVisibilityEvent.java    |   5 +-
 .../isis/applib/events/ParseValueEvent.java     |   5 +-
 .../isis/applib/events/PropertyAccessEvent.java |   5 +-
 .../isis/applib/events/PropertyModifyEvent.java |   5 +-
 .../applib/events/PropertyUsabilityEvent.java   |   5 +-
 .../applib/events/PropertyVisibilityEvent.java  |   5 +-
 .../isis/applib/events/ProposedHolderEvent.java |   5 +-
 .../isis/applib/events/UsabilityEvent.java      |   5 +-
 .../isis/applib/events/ValidityEvent.java       |   5 +-
 .../isis/applib/events/VisibilityEvent.java     |   5 +-
 .../i18n/TranslatableMessagesService.java       |  96 ++++++++
 .../services/i18n/TranslatableString.java       | 240 +++++++++++++++++++
 .../services/i18n/TranslationService.java       |  44 ++++
 .../applib/services/locale/LocaleProvider.java  |  29 +++
 .../isis/applib/services/i18n/TrStringTest.java | 123 ++++++++++
 .../facets/all/i18n/I18nFacetFactory.java       | 167 +++++++++++++
 .../cssclassfa/CssClassFaFacetAbstract.java     |  16 +-
 .../services/ServicesInjectorDefault.java       |  36 +--
 .../services/i18n/LocaleProviderDefault.java    |  36 +++
 .../i18n/TranslationServiceLogging.java         |  72 ++++++
 .../specloader/ObjectReflectorDefault.java      |  28 ++-
 .../dflt/ProgrammingModelFacetsJava5.java       |  13 +-
 .../services/ServicesInstallerFallback.java     |  52 ++++
 ...InstallerFromConfigurationAndAnnotation.java |  10 +
 .../src/main/webapp/WEB-INF/isis.properties     |  13 -
 39 files changed, 1017 insertions(+), 79 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/AccessEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/AccessEvent.java 
b/core/applib/src/main/java/org/apache/isis/applib/events/AccessEvent.java
index dd0f168..c4034ab 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/events/AccessEvent.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/events/AccessEvent.java
@@ -22,7 +22,7 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents an access (reading) of a property, collection or title.
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents an access (reading) of a property, collection or title.
  * 
  * <p>
  * Analogous to {@link ValidityEvent} (which corresponds to modifying a 
property
@@ -33,7 +33,10 @@ import org.apache.isis.applib.Identifier;
  * @see UsabilityEvent
  * @see VisibilityEvent
  * @see ValidityEvent
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public abstract class AccessEvent extends InteractionEvent {
 
     /**

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/ActionArgumentEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/ActionArgumentEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/ActionArgumentEvent.java
index 7f1f5b0..e497e8d 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/ActionArgumentEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/ActionArgumentEvent.java
@@ -22,7 +22,7 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check as to whether a particular argument for an action is 
valid
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check as to whether a particular argument for an action is valid
  * or not.
  * 
  * <p>
@@ -31,7 +31,10 @@ import org.apache.isis.applib.Identifier;
  * 
  * <p>
  * Called once per argument, and before {@link ActionInvocationEvent}.
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class ActionArgumentEvent extends ValidityEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/ActionInvocationEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/ActionInvocationEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/ActionInvocationEvent.java
index 6b97f95..d23022b 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/ActionInvocationEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/ActionInvocationEvent.java
@@ -22,7 +22,7 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check as to whether a particular argument for an action is 
valid
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check as to whether a particular argument for an action is valid
  * or not.
  * 
  * <p>
@@ -31,7 +31,10 @@ import org.apache.isis.applib.Identifier;
  * 
  * <p>
  * Called after each of the {@link ActionArgumentEvent}s.
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class ActionInvocationEvent extends ValidityEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/ActionUsabilityEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/ActionUsabilityEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/ActionUsabilityEvent.java
index 0e51e6c..772ab3e 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/ActionUsabilityEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/ActionUsabilityEvent.java
@@ -22,12 +22,15 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check as to whether an action is usable or has been disabled.
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check as to whether an action is usable or has been disabled.
  * 
  * <p>
  * If {@link #getReason()} is not <tt>null</tt> then provides the reason why 
the
  * action is disabled; otherwise action is enabled.
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class ActionUsabilityEvent extends UsabilityEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/ActionVisibilityEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/ActionVisibilityEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/ActionVisibilityEvent.java
index 1307f38..9e1199a 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/ActionVisibilityEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/ActionVisibilityEvent.java
@@ -22,12 +22,15 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check as to whether an action is visible or has been hidden.
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check as to whether an action is visible or has been hidden.
  * 
  * <p>
  * If {@link #getReason()} is not <tt>null</tt> then provides the reason why 
the
  * action is invisible; otherwise action is visible.
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class ActionVisibilityEvent extends VisibilityEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/CollectionAccessEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/CollectionAccessEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/CollectionAccessEvent.java
index e182e49..f3e5aab 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/CollectionAccessEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/CollectionAccessEvent.java
@@ -22,14 +22,17 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents an access (reading) of a collection.
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents an access (reading) of a collection.
  * 
  * <p>
  * Analogous to {@link CollectionAddToEvent} or
  * {@link CollectionRemoveFromEvent}, however the {@link #getReason()} will
  * always be <tt>null</tt>. (If access is not allowed then a vetoing
  * {@link CollectionVisibilityEvent} would have been fired).
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class CollectionAccessEvent extends AccessEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/CollectionAddToEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/CollectionAddToEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/CollectionAddToEvent.java
index a940bc4..3de08ce 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/CollectionAddToEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/CollectionAddToEvent.java
@@ -22,7 +22,7 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check as to whether a particular object to be added to a
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check as to whether a particular object to be added to a
  * collection is valid or not.
  * 
  * <p>
@@ -30,7 +30,10 @@ import org.apache.isis.applib.Identifier;
  * object is invalid; otherwise the object is valid.
  * 
  * @see CollectionRemoveFromEvent
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class CollectionAddToEvent extends ValidityEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/CollectionMethodEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/CollectionMethodEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/CollectionMethodEvent.java
index 93d50eb..be57bb1 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/CollectionMethodEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/CollectionMethodEvent.java
@@ -22,9 +22,11 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents an interaction with a collection object itself.
- * 
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents an interaction with a collection object itself.
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class CollectionMethodEvent extends AccessEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/CollectionRemoveFromEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/CollectionRemoveFromEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/CollectionRemoveFromEvent.java
index 909227c..d1372aa 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/CollectionRemoveFromEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/CollectionRemoveFromEvent.java
@@ -22,7 +22,7 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check as to whether a particular object to be removed from a
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check as to whether a particular object to be removed from a
  * collection is valid or not.
  * 
  * <p>
@@ -30,7 +30,10 @@ import org.apache.isis.applib.Identifier;
  * object is invalid; otherwise the object is valid.
  * 
  * @see CollectionAddToEvent
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class CollectionRemoveFromEvent extends ValidityEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/CollectionUsabilityEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/CollectionUsabilityEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/CollectionUsabilityEvent.java
index cff56b7..1b0fe6d 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/CollectionUsabilityEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/CollectionUsabilityEvent.java
@@ -22,12 +22,15 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check as to whether a collection is usable or has been 
disabled.
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check as to whether a collection is usable or has been disabled.
  * 
  * <p>
  * If {@link #getReason()} is not <tt>null</tt> then provides the reason why 
the
  * collection is disabled; otherwise collection is enabled.
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class CollectionUsabilityEvent extends UsabilityEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/CollectionVisibilityEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/CollectionVisibilityEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/CollectionVisibilityEvent.java
index f7965ae..cb0dabc 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/CollectionVisibilityEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/CollectionVisibilityEvent.java
@@ -22,12 +22,15 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check as to whether a collection is visible or has been hidden.
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check as to whether a collection is visible or has been hidden.
  * 
  * <p>
  * If {@link #getReason()} is not <tt>null</tt> then provides the reason why 
the
  * collection is invisible; otherwise collection is visible.
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class CollectionVisibilityEvent extends VisibilityEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/InteractionEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/InteractionEvent.java 
b/core/applib/src/main/java/org/apache/isis/applib/events/InteractionEvent.java
index 7b2b4c5..7905d6d 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/InteractionEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/InteractionEvent.java
@@ -25,13 +25,16 @@ import java.util.List;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents an interaction with a domain object or a particular feature
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents an interaction with a domain object or a particular feature
  * (property, collection, action) of a domain object.
  * 
  * <p>
  * Many of the interactions are checks for {@link VisibilityEvent visibility},
  * {@link UsabilityEvent usability} and {@link ValidityEvent validity}.
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public abstract class InteractionEvent extends EventObject {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/ObjectTitleEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/ObjectTitleEvent.java 
b/core/applib/src/main/java/org/apache/isis/applib/events/ObjectTitleEvent.java
index c4e19c2..b25f855 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/ObjectTitleEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/ObjectTitleEvent.java
@@ -22,12 +22,15 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents an access (reading) of an object's title.
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents an access (reading) of an object's title.
  * 
  * <p>
  * The {@link #getReason()} will always be <tt>null</tt>; access is always
  * allowed.
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class ObjectTitleEvent extends AccessEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/ObjectValidityEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/ObjectValidityEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/ObjectValidityEvent.java
index 0266196..0068b1c 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/ObjectValidityEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/ObjectValidityEvent.java
@@ -22,14 +22,17 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check as to whether the current values of the
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check as to whether the current values of the
  * properties/collections of an object are valid (for example, prior to saving
  * that object).
  * 
  * <p>
  * If {@link #getReason()} is not <tt>null</tt> then provides the reason why 
the
  * object is invalid, otherwise ok.
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class ObjectValidityEvent extends ValidityEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/ObjectVisibilityEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/ObjectVisibilityEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/ObjectVisibilityEvent.java
index 883b968..59ca9db 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/ObjectVisibilityEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/ObjectVisibilityEvent.java
@@ -22,12 +22,15 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check as to whether an object is visible or has been hidden.
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check as to whether an object is visible or has been hidden.
  * 
  * <p>
  * If {@link #getReason()} is not <tt>null</tt> then provides the reason why 
the
  * object is invisible; otherwise action is visible.
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class ObjectVisibilityEvent extends VisibilityEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/ParseValueEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/ParseValueEvent.java 
b/core/applib/src/main/java/org/apache/isis/applib/events/ParseValueEvent.java
index 36566b1..3bafcaa 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/ParseValueEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/ParseValueEvent.java
@@ -22,13 +22,16 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check as to whether the proposed values of the value type is
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check as to whether the proposed values of the value type is
  * valid.
  * 
  * <p>
  * If {@link #getReason()} is not <tt>null</tt> then provides the reason why 
the
  * proposed value is invalid, otherwise the new value is acceptable.
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class ParseValueEvent extends ValidityEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/PropertyAccessEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/PropertyAccessEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/PropertyAccessEvent.java
index 090f949..06d9f8c 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/PropertyAccessEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/PropertyAccessEvent.java
@@ -22,13 +22,16 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents an access (reading) of a property.
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents an access (reading) of a property.
  * 
  * <p>
  * Analogous to {@link PropertyModifyEvent}, however the {@link #getReason()}
  * will always be <tt>null</tt>. (If access is not allowed then a
  * {@link PropertyVisibilityEvent} would have been fired).
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class PropertyAccessEvent extends AccessEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/PropertyModifyEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/PropertyModifyEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/PropertyModifyEvent.java
index 33be17e..51ef377 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/PropertyModifyEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/PropertyModifyEvent.java
@@ -22,13 +22,16 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check as to whether a particular value for a property is valid
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check as to whether a particular value for a property is valid
  * or not.
  * 
  * <p>
  * If {@link #getReason()} is not <tt>null</tt> then provides the reason why 
the
  * value is invalid; otherwise the value is valid.
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class PropertyModifyEvent extends ValidityEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/PropertyUsabilityEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/PropertyUsabilityEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/PropertyUsabilityEvent.java
index a4a94d2..f4a8593 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/PropertyUsabilityEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/PropertyUsabilityEvent.java
@@ -22,12 +22,15 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check as to whether a property is usable or has been disabled.
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check as to whether a property is usable or has been disabled.
  * 
  * <p>
  * If {@link #getReason()} is not <tt>null</tt> then provides the reason why 
the
  * property is disabled; otherwise property is enabled.
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class PropertyUsabilityEvent extends UsabilityEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/PropertyVisibilityEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/PropertyVisibilityEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/PropertyVisibilityEvent.java
index c8b72e4..a0b538d 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/PropertyVisibilityEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/PropertyVisibilityEvent.java
@@ -22,12 +22,15 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check as to whether a property is visible or has been hidden.
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check as to whether a property is visible or has been hidden.
  * 
  * <p>
  * If {@link #getReason()} is not <tt>null</tt> then provides the reason why 
the
  * property is invisible; otherwise property is visible.
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public class PropertyVisibilityEvent extends VisibilityEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/ProposedHolderEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/ProposedHolderEvent.java
 
b/core/applib/src/main/java/org/apache/isis/applib/events/ProposedHolderEvent.java
index 5358bbb..292cc72 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/ProposedHolderEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/ProposedHolderEvent.java
@@ -20,10 +20,13 @@
 package org.apache.isis.applib.events;
 
 /**
- * Makes it easier to process different events that hold a single proposed
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> makes it 
easier to process different events that hold a single proposed
  * argument (such as {@link CollectionAddToEvent} and
  * {@link PropertyModifyEvent}).
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public interface ProposedHolderEvent {
 
     Object getProposed();

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/UsabilityEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/UsabilityEvent.java 
b/core/applib/src/main/java/org/apache/isis/applib/events/UsabilityEvent.java
index 7792bdd..0e6e6b6 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/UsabilityEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/UsabilityEvent.java
@@ -22,7 +22,7 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check to determine whether a member of an object is usable or
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check to determine whether a member of an object is usable or
  * has been disabled.
  * 
  * <p>
@@ -32,7 +32,10 @@ import org.apache.isis.applib.Identifier;
  * @see AccessEvent
  * @see VisibilityEvent
  * @see ValidityEvent
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public abstract class UsabilityEvent extends InteractionEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/ValidityEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/ValidityEvent.java 
b/core/applib/src/main/java/org/apache/isis/applib/events/ValidityEvent.java
index d452400..a50ad88 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/events/ValidityEvent.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/events/ValidityEvent.java
@@ -22,7 +22,7 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check to determine whether a proposed change is valid.
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check to determine whether a proposed change is valid.
  * 
  * <p>
  * Multiple subclasses, including:
@@ -41,7 +41,10 @@ import org.apache.isis.applib.Identifier;
  * @see AccessEvent
  * @see VisibilityEvent
  * @see UsabilityEvent
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public abstract class ValidityEvent extends InteractionEvent implements 
ProposedHolderEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/events/VisibilityEvent.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/events/VisibilityEvent.java 
b/core/applib/src/main/java/org/apache/isis/applib/events/VisibilityEvent.java
index a306b02..06908e6 100644
--- 
a/core/applib/src/main/java/org/apache/isis/applib/events/VisibilityEvent.java
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/events/VisibilityEvent.java
@@ -22,7 +22,7 @@ package org.apache.isis.applib.events;
 import org.apache.isis.applib.Identifier;
 
 /**
- * Represents a check to determine whether a member of an object is visible or
+ * <i>Supported only by {@link 
org.apache.isis.applib.services.wrapper.WrapperFactory} service, </i> 
represents a check to determine whether a member of an object is visible or
  * has been hidden.
  * 
  * <p>
@@ -32,7 +32,10 @@ import org.apache.isis.applib.Identifier;
  * @see AccessEvent
  * @see UsabilityEvent
  * @see ValidityEvent
+ *
+ * @deprecated - superceded by <code>domainEvent</code> support ({@link 
org.apache.isis.applib.services.eventbus.PropertyDomainEvent}, {@link 
org.apache.isis.applib.IsisApplibModule.CollectionDomainEvent}, {@link 
org.apache.isis.applib.services.eventbus.ActionDomainEvent}).
  */
+@Deprecated
 public abstract class VisibilityEvent extends InteractionEvent {
 
     private static final long serialVersionUID = 1L;

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslatableMessagesService.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslatableMessagesService.java
 
b/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslatableMessagesService.java
new file mode 100644
index 0000000..63b6fad
--- /dev/null
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslatableMessagesService.java
@@ -0,0 +1,96 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.applib.services.i18n;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Properties;
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+import org.apache.isis.applib.annotation.Action;
+import org.apache.isis.applib.annotation.ActionLayout;
+import org.apache.isis.applib.annotation.DomainService;
+import org.apache.isis.applib.annotation.DomainServiceLayout;
+import org.apache.isis.applib.annotation.NatureOfService;
+import org.apache.isis.applib.annotation.ParameterLayout;
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.annotation.SemanticsOf;
+import org.apache.isis.applib.value.Clob;
+
+@DomainService(
+        nature = NatureOfService.VIEW_MENU_ONLY
+)
+@DomainServiceLayout(
+        menuBar = DomainServiceLayout.MenuBar.SECONDARY,
+        named = "Prototyping"
+)
+public class TranslatableMessagesService {
+
+    @Programmatic
+    @PostConstruct
+    public void init(final Map<String,String> config) {
+        final Properties props = new Properties();
+        for (final String key : config.keySet()) {
+            props.put(key, config.get(key));
+        }
+        props.list(System.out);
+    }
+
+    @Programmatic
+    @PreDestroy
+    public void shutdown() {
+
+    }
+
+
+    @Action(
+            semantics = SemanticsOf.SAFE
+    )
+    @ActionLayout(
+            cssClassFa = "fa-download"
+    )
+    public Clob downloadPotFile(
+            @ParameterLayout(named = ".pot file name")
+            final String potFileName) {
+        final Map<String, Collection<String>> messages = 
translationService.messages();
+        final StringBuilder buf = new StringBuilder();
+        for (String message : messages.keySet()) {
+            final Collection<String> contexts = messages.get(message);
+            for (String context : contexts) {
+                buf.append("#: ").append(context).append("\n");
+            }
+            buf.append("msgid: \"").append(message).append("\"\n");
+            buf.append("msgstr: \"\"\n");
+            buf.append("\n\n\n");
+        }
+        return new Clob(potFileName, "text/plain", buf.toString());
+    }
+
+    public String default0DownloadPotFile() {
+        return "myapp.pot";
+    }
+
+    // //////////////////////////////////////
+
+
+    @Inject
+    private TranslationService translationService;
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslatableString.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslatableString.java
 
b/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslatableString.java
new file mode 100644
index 0000000..75b1a6e
--- /dev/null
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslatableString.java
@@ -0,0 +1,240 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.applib.services.i18n;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import com.google.common.collect.Lists;
+
+public final class TranslatableString {
+
+    //region > tr, trn (factory methods)
+
+    /**
+     * A translatable string supporting both singular and plural forms.
+     *
+     * @param pattern - pattern for all (singular and plural) form, with 
<code>${xxx}</code> placeholders
+     * @param paramArgs - parameter/argument pairs (string and object, string 
and object, ...)
+     */
+    public static TranslatableString tr(
+            final String pattern,
+            final Object... paramArgs) {
+        return new TranslatableString(Type.TR, pattern, null, 1, 
asMap(paramArgs));
+    }
+
+    /**
+     * A translatable string supporting both singular and plural forms.
+     *
+     * @param singularPattern - pattern for the singular form, with 
<code>${xxx}</code> placeholders
+     * @param pluralPattern - pattern for the singular form, with 
<code>${xxx}</code> placeholders
+     * @param number - whether to use singular or plural form when rendering
+     * @param paramArgs - parameter/argument pairs (string and object, string 
and object, ...)
+     */
+    public static TranslatableString trn(
+            final String singularPattern,
+            final String pluralPattern,
+            final int number,
+            final Object... paramArgs) {
+        return new TranslatableString(Type.TRN, singularPattern, 
pluralPattern, number, asMap(paramArgs));
+    }
+
+    /**
+     * Converts a list of objects [a, 1, b, 2] into a map {a -> 1; b -> 2}
+     */
+    private static Map<String, Object> asMap(final Object[] paramArgs) {
+        final HashMap<String, Object> map = new HashMap<String, Object>();
+        boolean param = true;
+        String paramStr = null;
+        for (final Object paramArg : paramArgs) {
+            if (param) {
+                if (paramArg instanceof String) {
+                    paramStr = (String) paramArg;
+                } else {
+                    throw new IllegalArgumentException("Parameter must be a 
string");
+                }
+            } else {
+                final Object arg = paramArg;
+                map.put(paramStr, arg);
+                paramStr = null;
+            }
+            param = !param;
+        }
+        if (paramStr != null) {
+            throw new IllegalArgumentException("Must have equal number of 
parameters and arguments");
+        }
+        return map;
+    }
+    //endregion
+
+
+    private final Type type;
+    private final String singularPattern;
+    private final String pluralPattern;
+    private final int number;
+    private final Map<String, Object> argumentsByParameterName;
+
+    private enum Type {
+        /**
+         * No plurals
+         */
+        TR {
+            @Override
+            public String toString(final TranslatableString trString) {
+                return "tr: " + trString.singularPattern;
+            }
+        },
+        /**
+         * With plurals
+         */
+        TRN {
+            @Override
+            public String toString(final TranslatableString trString) {
+                return "trn: " + trString.pluralPattern;
+            }
+        };
+
+        public abstract String toString(final TranslatableString trString);
+    }
+
+    private TranslatableString(
+            final Type type,
+            final String singularPattern,
+            final String pluralPattern,
+            final int number,
+            final Map<String, Object> argumentsByParameterName) {
+
+        this.type = type;
+        this.singularPattern = singularPattern;
+        this.pluralPattern = pluralPattern;
+        this.number = number;
+        this.argumentsByParameterName = argumentsByParameterName;
+    }
+
+    /**
+     * The pattern (or the singular pattern if {@link #isPluralForm()} is 
<code>true</code>).
+     */
+    String getSingularPattern() {
+        return singularPattern;
+    }
+
+    /**
+     * The plural pattern (but will be <code>null</code> if {@link 
#isPluralForm()} is <code>false</code>).
+     */
+    String getPluralPattern() {
+        return pluralPattern;
+    }
+
+    boolean isPluralForm() {
+        return type == Type.TRN;
+    }
+
+    /**
+     * The arguments; excluded from {@link #equals(Object)} comparison.
+     */
+    Map<String, Object> getArgumentsByParameterName() {
+        return argumentsByParameterName;
+    }
+
+    //region > equals, hashCode
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        final TranslatableString that = (TranslatableString) o;
+
+        if (pluralPattern != null ? !pluralPattern.equals(that.pluralPattern) 
: that.pluralPattern != null)
+            return false;
+        if (singularPattern != null ? 
!singularPattern.equals(that.singularPattern) : that.singularPattern != null)
+            return false;
+        if (type != that.type) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = type != null ? type.hashCode() : 0;
+        result = 31 * result + (singularPattern != null ? 
singularPattern.hashCode() : 0);
+        result = 31 * result + (pluralPattern != null ? 
pluralPattern.hashCode() : 0);
+        return result;
+    }
+
+    //endregion
+
+
+    //region > translate
+
+    public String translate(final TranslationService translationService, final 
String context, final Locale locale) {
+        final String translatedText = translationService.translate(context, 
getText(), locale);
+        return translated(translatedText);
+    }
+
+    /**
+     * The text to be translated; depends on whether {@link #isPluralForm()} 
and whether to be translated.
+     *
+     * <p>
+     *     May or may not hold placeholders.
+     * </p>
+     */
+    String getText() {
+        return !isPluralForm() || number == 1 ? getSingularPattern() : 
getPluralPattern();
+    }
+
+    String translated(final String translatedText) {
+        return format(translatedText, argumentsByParameterName);
+    }
+
+    static String format(String format, Map<String, Object> values)
+    {
+        StringBuilder formatter = new StringBuilder(format);
+        List<Object> valueList = Lists.newArrayList();
+
+        Matcher matcher = Pattern.compile("\\{(\\w+)}").matcher(format);
+
+        while (matcher.find())
+        {
+            String key = matcher.group(1);
+
+            String formatKey = String.format("{%s}", key);
+            int index = formatter.indexOf(formatKey);
+
+            if (index != -1)
+            {
+                formatter.replace(index, index + formatKey.length(), "%s");
+                valueList.add(values.get(key));
+            }
+        }
+
+        return String.format(formatter.toString(), valueList.toArray());
+    }
+
+    //endregion
+
+
+    @Override
+    public String toString() {
+        return type.toString(this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslationService.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslationService.java
 
b/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslationService.java
new file mode 100644
index 0000000..6204cdd
--- /dev/null
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslationService.java
@@ -0,0 +1,44 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.applib.services.i18n;
+
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Map;
+import org.apache.isis.applib.annotation.Programmatic;
+
+public interface TranslationService {
+
+    @Programmatic
+    public String translate(final String context, final String originalText, 
final Locale targetLocale);
+
+    /**
+     * Returns the set of messages encountered and cached by the service (the 
key of the map) along with a set of
+     * context strings (the value of the map)
+     *
+     * <p>
+     *     The intention is that an implementation running in prototype mode 
should retain all requests to
+     *     {@link #translate(String, String, java.util.Locale)}, such that 
they can be translated and used by the
+     *     same implementation in non-prototype mode.
+     * </p>
+     */
+    @Programmatic
+    public Map<String, Collection<String>> messages();
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/main/java/org/apache/isis/applib/services/locale/LocaleProvider.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/services/locale/LocaleProvider.java
 
b/core/applib/src/main/java/org/apache/isis/applib/services/locale/LocaleProvider.java
new file mode 100644
index 0000000..5a34285
--- /dev/null
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/services/locale/LocaleProvider.java
@@ -0,0 +1,29 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.applib.services.locale;
+
+import java.util.Locale;
+import org.apache.isis.applib.annotation.Programmatic;
+
+public interface LocaleProvider {
+
+    @Programmatic
+    Locale getLocale();
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/applib/src/test/java/org/apache/isis/applib/services/i18n/TrStringTest.java
----------------------------------------------------------------------
diff --git 
a/core/applib/src/test/java/org/apache/isis/applib/services/i18n/TrStringTest.java
 
b/core/applib/src/test/java/org/apache/isis/applib/services/i18n/TrStringTest.java
new file mode 100644
index 0000000..552f9e1
--- /dev/null
+++ 
b/core/applib/src/test/java/org/apache/isis/applib/services/i18n/TrStringTest.java
@@ -0,0 +1,123 @@
+package org.apache.isis.applib.services.i18n;
+
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class TrStringTest {
+
+    public static class GetText extends TrStringTest {
+
+        @Test
+        public void singularForm() throws Exception {
+            final TranslatableString ts = TranslatableString.tr("No, you can't 
do that!");
+
+            assertThat(ts.getText(), is("No, you can't do that!"));
+        }
+
+        @Test
+        public void pluralFormOne() throws Exception {
+            final TranslatableString ts = TranslatableString.trn("You can't do 
that because there is a dependent object", "You can't do that because there are 
dependent objects", 1);
+
+            assertThat(ts.getText(), is("You can't do that because there is a 
dependent object"));
+        }
+
+        @Test
+        public void pluralFormTwo() throws Exception {
+            final TranslatableString ts = TranslatableString.trn("You can't do 
that because there is a dependent object", "You can't do that because there are 
dependent objects", 2);
+
+            assertThat(ts.getText(), is("You can't do that because there are 
dependent objects"));
+        }
+
+    }
+
+    public static class Translated extends TrStringTest {
+
+        @Test
+        public void singularForm() throws Exception {
+            final TranslatableString ts = TranslatableString.tr("My name is 
{lastName}, {firstName} {lastName}.", "lastName", "Bond", "firstName", "James");
+
+            assertThat(ts.translated("Iche heisse {lastName}, {firstName} 
{lastName}."), is("Iche heisse Bond, James Bond."));
+        }
+
+        @Test
+        public void pluralFormOne() throws Exception {
+            final TranslatableString ts = TranslatableString.trn(
+                    "My name is {lastName}, {firstName} {lastName}.",
+                    "My name is {firstName} {lastName}.",
+                    1,
+                    "lastName", "Bond", "firstName", "James");
+
+            assertThat(ts.translated("Iche heisse {lastName}, {firstName} 
{lastName}."), is("Iche heisse Bond, James Bond."));
+        }
+    }
+
+    public static class Translate extends TrStringTest {
+
+        @Rule
+        public JUnitRuleMockery2 context = 
JUnitRuleMockery2.createFor(JUnitRuleMockery2.Mode.INTERFACES_AND_CLASSES);
+
+        private TranslationService echoTranslationService;
+
+        private String originalTextToTranslate;
+
+        @Before
+        public void setUp() throws Exception {
+            echoTranslationService = new TranslationService() {
+                @Override
+                public String translate(final String context, final String 
originalText, final Locale targetLocale) {
+                    originalTextToTranslate = originalText;
+                    return originalText;
+                }
+                @Override
+                public Map<String, Collection<String>> messages() {
+                    return null;
+                }
+            };
+        }
+
+        @Test
+        public void singularForm() throws Exception {
+            final TranslatableString ts = TranslatableString.tr("My name is 
{lastName}, {firstName} {lastName}.", "lastName", "Bond", "firstName", "James");
+
+            assertThat(ts.translate(echoTranslationService, null, null), 
is("My name is Bond, James Bond."));
+            assertThat(originalTextToTranslate, is("My name is {lastName}, 
{firstName} {lastName}."));
+        }
+
+        @Test
+        public void pluralFormOne() throws Exception {
+            final TranslatableString ts = TranslatableString.trn(
+                    "My name is {lastName}, {firstName} {lastName}.",
+                    "My name is {firstName} {lastName}.",
+                    1,
+                    "lastName", "Bond", "firstName", "James");
+
+            assertThat(ts.translate(echoTranslationService, null, null), 
is("My name is Bond, James Bond."));
+            assertThat(originalTextToTranslate, is("My name is {lastName}, 
{firstName} {lastName}."));
+        }
+
+        @Test
+        public void pluralFormTwo() throws Exception {
+            final TranslatableString ts = TranslatableString.trn(
+                    "My name is {lastName}, {firstName} {lastName}.",
+                    "My name is {firstName} {lastName}.",
+                    2,
+                    "lastName", "Bond", "firstName", "James");
+
+            assertThat(ts.translate(echoTranslationService, null, null), 
is("My name is James Bond."));
+            assertThat(originalTextToTranslate, is("My name is {firstName} 
{lastName}."));
+        }
+
+    }
+
+
+
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i18n/I18nFacetFactory.java
----------------------------------------------------------------------
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i18n/I18nFacetFactory.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i18n/I18nFacetFactory.java
new file mode 100644
index 0000000..6761b04
--- /dev/null
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i18n/I18nFacetFactory.java
@@ -0,0 +1,167 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.core.metamodel.facets.all.i18n;
+
+
+import java.util.Locale;
+import java.util.Objects;
+import com.google.common.base.Strings;
+import org.apache.isis.applib.services.i18n.TranslationService;
+import org.apache.isis.applib.services.locale.LocaleProvider;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facetapi.FacetUtil;
+import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.facetapi.IdentifiedHolder;
+import 
org.apache.isis.core.metamodel.facetdecorator.i18n.internal.DescribedAsFacetWrapI18n;
+import 
org.apache.isis.core.metamodel.facetdecorator.i18n.internal.NamedFacetWrapI18n;
+import org.apache.isis.core.metamodel.facets.ContributeeMemberFacetFactory;
+import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
+import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
+import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjectorAware;
+
+public class I18nFacetFactory extends FacetFactoryAbstract implements 
ContributeeMemberFacetFactory, ServicesInjectorAware {
+
+    private ServicesInjector servicesInjector;
+
+    private TranslationService translationService;
+    private LocaleProvider localeProvider;
+
+    public I18nFacetFactory() {
+        super(FeatureType.EVERYTHING);
+    }
+
+    @Override
+    public void process(final ProcessClassContext processClassContext) {
+        final FacetHolder facetHolder = processClassContext.getFacetHolder();
+        if(facetHolder instanceof IdentifiedHolder) {
+            final IdentifiedHolder holder = (IdentifiedHolder) facetHolder;
+            final String context = 
holder.getIdentifier().toClassIdentityString();
+            translateName(holder, context);
+            translateDescription(holder, context);
+        }
+    }
+
+    @Override
+    public void process(final ProcessMethodContext processMethodContext) {
+        final IdentifiedHolder holder = processMethodContext.getFacetHolder();
+
+        final String context = 
holder.getIdentifier().toClassAndNameIdentityString();
+        translateName(holder, context);
+        translateDescription(holder, context);
+    }
+
+    @Override
+    public void processParams(final ProcessParameterContext 
processParameterContext) {
+        final IdentifiedHolder holder = 
processParameterContext.getFacetHolder();
+
+        final String context = holder.getIdentifier().toFullIdentityString();
+        translateName(holder, context);
+        translateDescription(holder, context);
+    }
+
+    @Override
+    public void process(final ProcessContributeeMemberContext 
processMemberContext) {
+        final IdentifiedHolder holder = processMemberContext.getFacetHolder();
+
+        final String context = 
holder.getIdentifier().toClassAndNameIdentityString();
+        translateName(holder, context);
+        translateDescription(holder, context);
+    }
+
+    // //////////////////////////////////////
+
+    void translateName(final IdentifiedHolder facetHolder, final String 
context) {
+        final NamedFacet facet = facetHolder.getFacet(NamedFacet.class);
+        if(facet == null) {
+            // not expected...
+            return;
+        }
+        final String originalText = facet.value();
+        if (isNullOrEmptyWhenTrimmed(originalText)) {
+            // not expected...
+            return;
+        }
+
+        final TranslationService translationService = 
lookupTranslationService();
+        final LocaleProvider localeProvider = lookupLocaleProvider();
+
+        final Locale locale = localeProvider.getLocale();
+        final String translatedText = translationService.translate(context, 
originalText, locale);
+        if(!Objects.equals(originalText, translatedText)) {
+            FacetUtil.addFacet(new NamedFacetWrapI18n(translatedText, 
facetHolder));
+        }
+    }
+
+    void translateDescription(final FacetHolder facetHolder, final String 
context) {
+
+        final IdentifiedHolder holder = (IdentifiedHolder) facetHolder;
+        final DescribedAsFacet facet = 
facetHolder.getFacet(DescribedAsFacet.class);
+        if(facet == null) {
+            return;
+        }
+        final String originalText = facet.value();
+        if (isNullOrEmptyWhenTrimmed(originalText)) {
+            return;
+        }
+
+        final TranslationService translationService = 
lookupTranslationService();
+        final LocaleProvider localeProvider = lookupLocaleProvider();
+
+        final Locale locale = localeProvider.getLocale();
+        final String translatedText = translationService.translate(context, 
originalText, locale);
+        if(!Objects.equals(originalText, translatedText)) {
+            FacetUtil.addFacet(new DescribedAsFacetWrapI18n(translatedText, 
holder));
+        }
+    }
+
+    private boolean isNullOrEmptyWhenTrimmed(final String originalText) {
+        return originalText == null || 
Strings.isNullOrEmpty(originalText.trim());
+    }
+
+    // //////////////////////////////////////
+
+    /**
+     * Looks up from {@link 
org.apache.isis.core.metamodel.runtimecontext.ServicesInjector}, otherwise 
defaults to
+     * {@link 
org.apache.isis.core.metamodel.services.i18n.LocaleProviderDefault}.
+     */
+    LocaleProvider lookupLocaleProvider() {
+        if(localeProvider == null) {
+            localeProvider = 
servicesInjector.lookupService(LocaleProvider.class);
+        }
+        return localeProvider;
+    }
+
+    /**
+     * Looks up from {@link 
org.apache.isis.core.metamodel.runtimecontext.ServicesInjector}, otherwise 
defaults to
+     * {@link 
org.apache.isis.core.metamodel.services.i18n.TranslationServiceLogging}.
+     */
+    TranslationService lookupTranslationService() {
+        if(translationService == null) {
+            translationService = 
servicesInjector.lookupService(TranslationService.class);
+        }
+        return translationService;
+    }
+
+    @Override
+    public void setServicesInjector(final ServicesInjector servicesInjector) {
+        this.servicesInjector = servicesInjector;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/cssclassfa/CssClassFaFacetAbstract.java
----------------------------------------------------------------------
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/cssclassfa/CssClassFaFacetAbstract.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/cssclassfa/CssClassFaFacetAbstract.java
index 99fba5f..963b860 100644
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/cssclassfa/CssClassFaFacetAbstract.java
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/cssclassfa/CssClassFaFacetAbstract.java
@@ -51,17 +51,23 @@ public class CssClassFaFacetAbstract extends 
SingleStringValueFacetAbstract impl
      * @param value The original CSS classes defined with {@literal @}{@link 
org.apache.isis.applib.annotation.CssClassFa CssClassFa}
      * @return The original CSS classes plus <em>fa</em> and <em>fa-fw</em> if 
not already provided
      */
-    static String sanitize(String value) {
-        Iterable<String> classes = Splitter.on(WHITESPACE).split(value);
-        Set<String> cssClassesSet = Sets.newLinkedHashSet();
+    static String sanitize(final String value) {
+        final Iterable<String> classes = Splitter.on(WHITESPACE).split(value);
+        final Set<String> cssClassesSet = Sets.newLinkedHashSet();
         cssClassesSet.add("fa");
         cssClassesSet.add("fa-fw");
-        for (String cssClass : classes) {
-            cssClassesSet.add(cssClass);
+        for (final String cssClass : classes) {
+            cssClassesSet.add(faPrefix(cssClass));
         }
         return Joiner.on(' ').join(cssClassesSet).trim();
     }
 
+    private static String faPrefix(final String cssClass) {
+        return cssClass.startsWith("fa-")
+                ? cssClass
+                : "fa-" + cssClass;
+    }
+
     public static Class<? extends Facet> type() {
         return CssClassFaFacet.class;
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/ServicesInjectorDefault.java
----------------------------------------------------------------------
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/ServicesInjectorDefault.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/ServicesInjectorDefault.java
index da67329..409f8c5 100644
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/ServicesInjectorDefault.java
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/ServicesInjectorDefault.java
@@ -71,7 +71,7 @@ public class ServicesInjectorDefault implements 
ServicesInjectorSpi, Specificati
      * </p>
      * @param injectorMethodEvaluator
      */
-    public ServicesInjectorDefault(InjectorMethodEvaluator 
injectorMethodEvaluator) {
+    public ServicesInjectorDefault(final InjectorMethodEvaluator 
injectorMethodEvaluator) {
         this.injectorMethodEvaluator = injectorMethodEvaluator != null ? 
injectorMethodEvaluator : new InjectorMethodEvaluatorDefault();
     }
 
@@ -187,7 +187,7 @@ public class ServicesInjectorDefault implements 
ServicesInjectorSpi, Specificati
         final List<Field> fields = Arrays.asList(cls.getDeclaredFields());
         final Iterable<Field> injectFields = Iterables.filter(fields, new 
Predicate<Field>() {
             @Override
-            public boolean apply(Field input) {
+            public boolean apply(final Field input) {
                 final Inject annotation = 
input.getAnnotation(javax.inject.Inject.class);
                 return annotation != null;
             }
@@ -204,10 +204,10 @@ public class ServicesInjectorDefault implements 
ServicesInjectorSpi, Specificati
         }
     }
 
-    private void autowire(Object object, Field field, List<Object> services) {
+    private void autowire(final Object object, final Field field, final 
List<Object> services) {
         for (final Object service : services) {
             final Class<?> serviceClass = service.getClass();
-            boolean canInject = isInjectorFieldFor(field, serviceClass);
+            final boolean canInject = isInjectorFieldFor(field, serviceClass);
             if(canInject) {
                 field.setAccessible(true);
                 invokeInjectorField(field, object, service);
@@ -219,7 +219,7 @@ public class ServicesInjectorDefault implements 
ServicesInjectorSpi, Specificati
     private void autowireViaPrefixedMethods(final Object object, final 
List<Object> services, final Class<?> cls, final String prefix) {
         final List<Method> methods = Arrays.asList(cls.getMethods());
         final Iterable<Method> prefixedMethods = Iterables.filter(methods, new 
Predicate<Method>(){
-            public boolean apply(Method method) {
+            public boolean apply(final Method method) {
                 final String methodName = method.getName();
                 return methodName.startsWith(prefix);
             }
@@ -230,10 +230,10 @@ public class ServicesInjectorDefault implements 
ServicesInjectorSpi, Specificati
         }
     }
 
-    private void autowire(Object object, Method prefixedMethod, List<Object> 
services) {
+    private void autowire(final Object object, final Method prefixedMethod, 
final List<Object> services) {
         for (final Object service : services) {
             final Class<?> serviceClass = service.getClass();
-            boolean isInjectorMethod = 
injectorMethodEvaluator.isInjectorMethodFor(prefixedMethod, serviceClass);
+            final boolean isInjectorMethod = 
injectorMethodEvaluator.isInjectorMethodFor(prefixedMethod, serviceClass);
             if(isInjectorMethod) {
                 prefixedMethod.setAccessible(true);
                 invokeInjectorMethod(prefixedMethod, object, service);
@@ -271,9 +271,9 @@ public class ServicesInjectorDefault implements 
ServicesInjectorSpi, Specificati
     private static void invokeInjectorField(final Field field, final Object 
target, final Object parameter) {
         try {
             field.set(target, parameter);
-        } catch (IllegalArgumentException e) {
+        } catch (final IllegalArgumentException e) {
             throw new MetaModelException(e);
-        } catch (IllegalAccessException e) {
+        } catch (final IllegalAccessException e) {
             throw new MetaModelException(String.format("Cannot access the %s 
field in %s", field.getName(), target.getClass().getName()));
         }
         if (LOG.isDebugEnabled()) {
@@ -299,38 +299,38 @@ public class ServicesInjectorDefault implements 
ServicesInjectorSpi, Specificati
     //region > lookupService, lookupServices
 
     @Override
-    public <T> T lookupService(Class<T> serviceClass) {
-        List<T> services = lookupServices(serviceClass);
+    public <T> T lookupService(final Class<T> serviceClass) {
+        final List<T> services = lookupServices(serviceClass);
         return !services.isEmpty() ? services.get(0) : null;
     }
 
     @SuppressWarnings("unchecked")
     @Override
-    public <T> List<T> lookupServices(Class<T> serviceClass) {
+    public <T> List<T> lookupServices(final Class<T> serviceClass) {
         locateAndCache(serviceClass);
         return (List<T>) servicesByType.get(serviceClass);
     };
 
-    private void locateAndCache(Class<?> serviceClass) {
+    private void locateAndCache(final Class<?> serviceClass) {
         if(servicesByType.containsKey(serviceClass)) {
            return; 
         }
 
-        List<Object> matchingServices = Lists.newArrayList();
+        final List<Object> matchingServices = Lists.newArrayList();
         addAssignableTo(serviceClass, services, matchingServices);
 
         servicesByType.put(serviceClass, matchingServices);
     }
 
-    private static void addAssignableTo(Class<?> type, List<Object> 
candidates, List<Object> filteredServicesAndContainer) {
-        Iterable<Object> filteredServices = Iterables.filter(candidates, 
ofType(type));
+    private static void addAssignableTo(final Class<?> type, final 
List<Object> candidates, final List<Object> filteredServicesAndContainer) {
+        final Iterable<Object> filteredServices = Iterables.filter(candidates, 
ofType(type));
         
filteredServicesAndContainer.addAll(Lists.newArrayList(filteredServices));
     }
 
     private static final Predicate<Object> ofType(final Class<?> cls) {
         return new Predicate<Object>() {
             @Override
-            public boolean apply(Object input) {
+            public boolean apply(final Object input) {
                 return cls.isAssignableFrom(input.getClass());
             }
         };
@@ -343,7 +343,7 @@ public class ServicesInjectorDefault implements 
ServicesInjectorSpi, Specificati
     private InjectorMethodEvaluator injectorMethodEvaluator;
 
     @Override
-    public void setSpecificationLookup(SpecificationLoader 
specificationLookup) {
+    public void setSpecificationLookup(final SpecificationLoader 
specificationLookup) {
         injectorMethodEvaluator = specificationLookup;
     }
 

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/LocaleProviderDefault.java
----------------------------------------------------------------------
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/LocaleProviderDefault.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/LocaleProviderDefault.java
new file mode 100644
index 0000000..4b7ec49
--- /dev/null
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/LocaleProviderDefault.java
@@ -0,0 +1,36 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.core.metamodel.services.i18n;
+
+
+import java.util.Locale;
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.services.locale.LocaleProvider;
+
+/**
+ * Not annotated with &#64;DomainService, but is registered as a fallback by 
<tt>ServicesInstallerFallback</tt>.
+ */
+public class LocaleProviderDefault implements LocaleProvider {
+
+    @Programmatic
+    @Override
+    public Locale getLocale() {
+        return Locale.getDefault();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/TranslationServiceLogging.java
----------------------------------------------------------------------
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/TranslationServiceLogging.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/TranslationServiceLogging.java
new file mode 100644
index 0000000..e4f8467
--- /dev/null
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/TranslationServiceLogging.java
@@ -0,0 +1,72 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.core.metamodel.services.i18n;
+
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NavigableSet;
+import com.google.common.collect.TreeMultimap;
+import org.joda.time.LocalDateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.services.i18n.TranslationService;
+
+/**
+ * Not annotated with &#64;DomainService, but is registered as a fallback by 
<tt>ServicesInstallerFallback</tt>.
+ */
+public class TranslationServiceLogging implements TranslationService {
+
+    public static Logger LOG = 
LoggerFactory.getLogger(TranslationServiceLogging.class);
+
+    private final TreeMultimap<String, String> messages = 
TreeMultimap.create();
+
+    public TranslationServiceLogging() {
+
+        LOG.info("");
+        LOG.info("");
+        LOG.info("");
+        
LOG.info("################################################################################");
+        LOG.info("#");
+        LOG.info("# " + LocalDateTime.now().toString("yyyy-MM-dd HH:mm:ss"));
+        LOG.info("#");
+        
LOG.info("################################################################################");
+        LOG.info("");
+        LOG.info("");
+
+    }
+
+    @Override
+    @Programmatic
+    public String translate(final String context, final String originalText, 
final Locale targetLocale) {
+
+        final NavigableSet<String> contexts = messages.get(originalText);
+        final boolean added = contexts.add(context);
+        if(added) {
+            LOG.info(String.format("%s_%s: %s", targetLocale.getISO3Country(), 
targetLocale.getISO3Language(), originalText));
+        }
+        return originalText;
+    }
+
+    @Programmatic
+    public Map<String, Collection<String>> messages() {
+        return messages.asMap();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/67e234b8/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ObjectReflectorDefault.java
----------------------------------------------------------------------
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ObjectReflectorDefault.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ObjectReflectorDefault.java
index ce25255..a27a1bf 100644
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ObjectReflectorDefault.java
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ObjectReflectorDefault.java
@@ -490,25 +490,19 @@ public final class ObjectReflectorDefault implements 
SpecificationLoaderSpi, App
     }
 
     public ObjectSpecification introspectIfRequired(final ObjectSpecification 
spec) {
+
         final ObjectSpecificationAbstract specSpi = 
(ObjectSpecificationAbstract)spec;
         final IntrospectionState introspectionState = 
specSpi.getIntrospectionState();
 
+        // REVIEW: can't remember why this is done in multiple passes, could 
it be simplified?
         if (introspectionState == IntrospectionState.NOT_INTROSPECTED) {
+
             
specSpi.setIntrospectionState(IntrospectionState.BEING_INTROSPECTED);
-            
-            specSpi.introspectTypeHierarchyAndMembers();
-            facetDecoratorSet.decorate(spec);
-            specSpi.updateFromFacetValues();
-            
-            specSpi.setIntrospectionState(IntrospectionState.INTROSPECTED);
+            introspect(specSpi);
+
         } else if (introspectionState == 
IntrospectionState.BEING_INTROSPECTED) {
-            // nothing to do
 
-            specSpi.introspectTypeHierarchyAndMembers();
-            facetDecoratorSet.decorate(spec);
-            specSpi.updateFromFacetValues();
-            
-            specSpi.setIntrospectionState(IntrospectionState.INTROSPECTED);
+            introspect(specSpi);
 
         } else if (introspectionState == IntrospectionState.INTROSPECTED) {
             // nothing to do
@@ -516,6 +510,13 @@ public final class ObjectReflectorDefault implements 
SpecificationLoaderSpi, App
         return spec;
     }
 
+    private void introspect(final ObjectSpecificationAbstract specSpi) {
+        specSpi.introspectTypeHierarchyAndMembers();
+        facetDecoratorSet.decorate(specSpi);
+        specSpi.updateFromFacetValues();
+        specSpi.setIntrospectionState(IntrospectionState.INTROSPECTED);
+    }
+
     @Override
     public ObjectSpecification lookupBySpecId(ObjectSpecId objectSpecId) {
         return getCache().getByObjectType(objectSpecId);
@@ -628,7 +629,8 @@ public final class ObjectReflectorDefault implements 
SpecificationLoaderSpi, App
                 return o.getClass();
             }
         });
-        return Collections.unmodifiableList(serviceClasses);
+        // take a copy, to allow eg I18nFacetFactory to add in default 
implementations of missing services.
+        return 
Collections.unmodifiableList(Lists.newArrayList(serviceClasses));
     }
 
     @Override

Reply via email to