You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2021/06/22 17:59:30 UTC

[isis] branch master updated: ISIS-1720: remove PluralFacet

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new 32d9c93  ISIS-1720: remove PluralFacet
32d9c93 is described below

commit 32d9c93ff1fa610a5af92639d8c3155523ce3ee7
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Jun 22 19:59:14 2021 +0200

    ISIS-1720: remove PluralFacet
    
    instead introduces a NounForm enum, so plural logic can be transferred
    to NamedFacet
---
 ...nthesizedMergedAnnotationInvocationHandler.java |  29 ++---
 .../NamedFacetForActionLayoutAnnotation.java       |   5 +-
 .../actions/layout/NamedFacetForActionXml.java     |   5 +-
 .../facets/all/describedas/DescribedAsFacet.java   |  13 +-
 .../all/describedas/DescribedAsFacetAbstract.java  |  21 +++-
 .../metamodel/facets/all/i8n/HasTranslation.java   |  22 +++-
 .../metamodel/facets/all/i8n/I8nFacetAbstract.java |  68 ++++++++---
 .../all/i8n/NounForm.java}                         |  26 +++-
 .../core/metamodel/facets/all/i8n/NounForms.java   | 134 +++++++++++++++++++++
 .../metamodel/facets/all/named/NamedFacet.java     |   5 +-
 .../facets/all/named/NamedFacetAbstract.java       |  18 ++-
 .../facets/all/named/NamedFacetForMemberName.java  |  10 +-
 .../named/NamedFacetFromMemberNameAbstract.java    |  20 ++-
 .../NamedFacetForCollectionLayoutAnnotation.java   |   5 +-
 .../layout/NamedFacetForCollectionXml.java         |   5 +-
 .../facets/fallback/FallbackFacetFactory.java      |   3 +-
 .../fallback/NamedFacetFallbackFromMemberName.java |   4 +-
 .../metamodel/facets/fallback/NamedFacetNone.java  |   6 +-
 .../DomainObjectLayoutFacetFactory.java            |   4 -
 .../NamedFacetForDomainObjectLayoutAnnotation.java |  43 +++++--
 .../NamedFacetForDomainObjectXml.java              |  37 ++++--
 ...PluralFacetForDomainObjectLayoutAnnotation.java |  45 -------
 .../PluralFacetForDomainObjectXml.java             |  47 --------
 .../DomainServiceLayoutFacetFactory.java           |  11 +-
 ...NamedFacetForDomainServiceLayoutAnnotation.java |  40 +++++-
 .../metamodel/facets/object/icon/IconFacet.java    |   2 -
 .../facets/object/plural/PluralFacet.java          |  49 --------
 .../facets/object/plural/PluralFacetAbstract.java  |  44 -------
 .../plural/inferred/PluralFacetInferred.java       |  32 -----
 .../metamodel/facets/object/title/TitleFacet.java  |   6 +-
 .../mandatory/MandatoryFacetAbstract.java          |   3 +-
 .../NamedFacetForParameterLayoutAnnotation.java    |  17 ++-
 .../NamedFacetForParameterUsingReflection.java     |   5 +-
 .../NamedFacetForPropertyLayoutAnnotation.java     |  15 ++-
 .../propertylayout/NamedFacetForPropertyXml.java   |  35 ++++--
 .../core/metamodel/layout/LayoutFacetUtil.java     |  21 ++--
 .../all/i18n/TranslationPostProcessor.java         |   8 +-
 .../services/grid/GridSystemServiceAbstract.java   |   2 -
 .../core/metamodel/spec/ObjectSpecification.java   |   6 +-
 .../core/metamodel/spec/feature/ObjectAction.java  |  35 ++----
 .../specloader/specimpl/ObjectActionMixedIn.java   |   5 +-
 .../specimpl/ObjectActionParameterAbstract.java    |  12 +-
 .../specloader/specimpl/ObjectMemberAbstract.java  |   6 +-
 .../specimpl/ObjectSpecificationAbstract.java      |  35 +++---
 .../specimpl/OneToManyAssociationMixedIn.java      |   3 +-
 .../specimpl/OneToOneAssociationMixedIn.java       |   3 +-
 .../specimpl/dflt/ObjectSpecificationDefault.java  |  30 ++---
 .../DescribedAsFacetOnStandaloneList.java          |  31 -----
 .../PluralFacetOnStandaloneList.java               |  31 -----
 ...etForCollectionLayoutAnnotationFactoryTest.java |   5 +-
 .../DomainObjectLayoutFactoryTest.java             |  27 ++---
 ...cetForParameterLayoutAnnotationFactoryTest.java |   6 +-
 .../facets/param/name/ParameterNameFacetTest.java  |   5 +-
 ...acetForPropertyLayoutAnnotationFactoryTest.java |   5 +-
 .../objects/ObjectActionLayoutXmlDefaultTest.java  |  12 +-
 .../objects/OneToManyAssociationDefaultTest.java   |   8 +-
 .../SpecificationLoaderTestAbstract.java           |   8 --
 ...ionParameterAbstractTest_getId_and_getName.java |   9 +-
 .../menubars/bootstrap3/MenuBarsServiceBS3.java    |  54 +++++----
 .../ObjectReflectorDefaultTest_object.java         |   2 -
 .../SpecificationLoaderTestAbstract.java           |   7 --
 .../common/model/action/ActionUiMetaModel.java     |   2 +-
 .../models/EntityCollectionModelStandalone.java    |   7 +-
 63 files changed, 637 insertions(+), 582 deletions(-)

diff --git a/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Annotations_SynthesizedMergedAnnotationInvocationHandler.java b/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Annotations_SynthesizedMergedAnnotationInvocationHandler.java
index 66bf8c0..66d826b 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Annotations_SynthesizedMergedAnnotationInvocationHandler.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Annotations_SynthesizedMergedAnnotationInvocationHandler.java
@@ -24,7 +24,6 @@ import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.util.Arrays;
-import java.util.NoSuchElementException;
 import java.util.Objects;
 
 import org.springframework.core.annotation.AnnotationConfigurationException;
@@ -66,8 +65,8 @@ implements InvocationHandler {
 
 
     private _Annotations_SynthesizedMergedAnnotationInvocationHandler(
-            MergedAnnotations mergedAnnotations,
-            Class<A> type) {
+            final MergedAnnotations mergedAnnotations,
+            final Class<A> type) {
 
         Assert.notNull(mergedAnnotations, "MergedAnnotations must not be null");
         Assert.notNull(type, "Type must not be null");
@@ -83,7 +82,7 @@ implements InvocationHandler {
 
 
     @Override
-    public Object invoke(Object proxy, Method method, Object[] args) {
+    public Object invoke(final Object proxy, final Method method, final Object[] args) {
         if (ReflectionUtils.isEqualsMethod(method)) {
             return annotationEquals(args[0]);
         }
@@ -103,7 +102,7 @@ implements InvocationHandler {
                 "Method [%s] is unsupported for synthesized annotation type [%s]", method, this.type));
     }
 
-    private boolean isAnnotationTypeMethod(Method method) {
+    private boolean isAnnotationTypeMethod(final Method method) {
         return (Objects.equals(method.getName(), "annotationType") && method.getParameterCount() == 0);
     }
 
@@ -111,7 +110,7 @@ implements InvocationHandler {
      * See {@link Annotation#equals(Object)} for a definition of the required algorithm.
      * @param other the other object to compare against
      */
-    private boolean annotationEquals(Object other) {
+    private boolean annotationEquals(final Object other) {
         if (this == other) {
             return true;
         }
@@ -151,7 +150,7 @@ implements InvocationHandler {
         return hashCode;
     }
 
-    private int getValueHashCode(Object value) {
+    private int getValueHashCode(final Object value) {
         // Use Arrays.hashCode since ObjectUtils doesn't comply to to
         // Annotation#hashCode()
         if (value instanceof boolean[]) {
@@ -184,7 +183,8 @@ implements InvocationHandler {
         return value.hashCode();
     }
 
-    private Object getAttributeValue(Method method) {
+    @Nullable
+    private Object getAttributeValue(final Method method) {
         String name = method.getName();
         Class<?> type = ClassUtils.resolvePrimitiveIfNecessary(method.getReturnType());
 
@@ -198,20 +198,13 @@ implements InvocationHandler {
         .findFirst()
         .orElse(defaultValue);
 
-        if(attributeValue==null) {
-            throw new NoSuchElementException("No value found for attribute named '" + name +
-                    "' in merged annotation " + this.type.getName());
-        }
-
         return attributeValue;
-
-
     }
 
     @SuppressWarnings("unchecked")
     static <A extends Annotation> A createProxy(
-            MergedAnnotations mergedAnnotations,
-            Class<A> type) {
+            final MergedAnnotations mergedAnnotations,
+            final Class<A> type) {
 
         ClassLoader classLoader = type.getClassLoader();
         InvocationHandler handler =
@@ -221,7 +214,7 @@ implements InvocationHandler {
         return (A) Proxy.newProxyInstance(classLoader, interfaces, handler);
     }
 
-    private static boolean isVisible(ClassLoader classLoader, Class<?> interfaceClass) {
+    private static boolean isVisible(final ClassLoader classLoader, final Class<?> interfaceClass) {
         try {
             return Class.forName(interfaceClass.getName(), false, classLoader) == interfaceClass;
         }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/NamedFacetForActionLayoutAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/NamedFacetForActionLayoutAnnotation.java
index 1cced22..4e9d61c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/NamedFacetForActionLayoutAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/NamedFacetForActionLayoutAnnotation.java
@@ -24,6 +24,7 @@ import java.util.Optional;
 import org.apache.isis.applib.annotation.ActionLayout;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
 
 public class NamedFacetForActionLayoutAnnotation extends NamedFacetAbstract {
@@ -38,9 +39,9 @@ public class NamedFacetForActionLayoutAnnotation extends NamedFacetAbstract {
                 .map(named -> new NamedFacetForActionLayoutAnnotation(named, holder));
     }
 
-    private NamedFacetForActionLayoutAnnotation(final String value, final FacetHolder holder) {
+    private NamedFacetForActionLayoutAnnotation(final String singularName, final FacetHolder holder) {
 
-        super(value, /*escaped*/ true, holder);
+        super(NounForms.preferredSingular().singular(singularName).build(), /*escaped*/ true, holder);
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/NamedFacetForActionXml.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/NamedFacetForActionXml.java
index ee780d3..ab947f2 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/NamedFacetForActionXml.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/NamedFacetForActionXml.java
@@ -24,6 +24,7 @@ import java.util.Optional;
 import org.apache.isis.applib.layout.component.ActionLayoutData;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
 
@@ -43,9 +44,9 @@ extends NamedFacetAbstract {
                 : Optional.empty();
     }
 
-    private NamedFacetForActionXml(final String value, final boolean escaped, final FacetHolder holder) {
+    private NamedFacetForActionXml(final String singularName, final boolean escaped, final FacetHolder holder) {
 
-        super(value, escaped, holder);
+        super(NounForms.preferredSingular().singular(singularName).build(), escaped, holder);
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/describedas/DescribedAsFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/describedas/DescribedAsFacet.java
index 713fb9c..c9e039b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/describedas/DescribedAsFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/describedas/DescribedAsFacet.java
@@ -29,6 +29,17 @@ import org.apache.isis.core.metamodel.facets.all.i8n.HasTranslation;
  * In the standard Apache Isis Programming Model, corresponds to annotating the
  * member with <tt>@DescribedAs</tt>.
  */
-public interface DescribedAsFacet extends Facet, HasTranslation {
+public interface DescribedAsFacet
+extends
+    Facet,
+    HasTranslation {
+
+    default String text() {
+        return preferredText();
+    }
+
+    default String translated() {
+        return preferredTranslated();
+    }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/describedas/DescribedAsFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/describedas/DescribedAsFacetAbstract.java
index 1d39658..5de53d3 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/describedas/DescribedAsFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/describedas/DescribedAsFacetAbstract.java
@@ -22,6 +22,8 @@ package org.apache.isis.core.metamodel.facets.all.describedas;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.all.i8n.I8nFacetAbstract;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
 
 public abstract class DescribedAsFacetAbstract
 extends I8nFacetAbstract
@@ -31,14 +33,25 @@ implements DescribedAsFacet {
         return DescribedAsFacet.class;
     }
 
-    protected DescribedAsFacetAbstract(String originalText, FacetHolder holder) {
+    protected DescribedAsFacetAbstract(
+            final String originalText,
+            final FacetHolder holder) {
         this(originalText, holder, Precedence.DEFAULT);
     }
 
-    protected DescribedAsFacetAbstract(String originalText, FacetHolder holder, final Facet.Precedence precedence) {
-        super(type(), originalText, holder, precedence);
+    protected DescribedAsFacetAbstract(
+            final String originalText,
+            final FacetHolder holder,
+            final Facet.Precedence precedence) {
+        super(type(),
+                NounForms
+                    .builder()
+                    .indifferent(originalText)
+                    .preferredNounForm(NounForm.INDIFFERENT)
+                    .build(),
+                holder,
+                precedence);
     }
 
-
 }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/HasTranslation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/HasTranslation.java
index 17cd470..d04da8f 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/HasTranslation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/HasTranslation.java
@@ -1,15 +1,33 @@
 package org.apache.isis.core.metamodel.facets.all.i8n;
 
+import org.apache.isis.commons.collections.ImmutableEnumSet;
+
 public interface HasTranslation {
 
     /**
+     * Originating text of preferred NounForm to be translated before use in the UI.
+     */
+    String preferredText();
+
+    /**
+     * Translated text of preferred NounForm to be used in the UI.
+     */
+    String preferredTranslated();
+
+    /**
      * Originating text to be translated before use in the UI.
      */
-    String text();
+    String text(NounForm nounForm);
 
     /**
      * Translated text to be used in the UI.
      */
-    String translated();
+    String translated(NounForm nounForm);
+
+    ImmutableEnumSet<NounForm> getSupportedNounForms();
+
+    default void memoizeTranslations() {
+        getSupportedNounForms().forEach(this::translated);
+    }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/I8nFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/I8nFacetAbstract.java
index dfdcab8..02791f3 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/I8nFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/I8nFacetAbstract.java
@@ -4,11 +4,13 @@ import java.util.Objects;
 import java.util.function.BiConsumer;
 
 import org.apache.isis.applib.services.i18n.TranslationContext;
+import org.apache.isis.commons.collections.ImmutableEnumSet;
 import org.apache.isis.commons.internal.base._Lazy;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetAbstract;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 
+import lombok.Getter;
 import lombok.NonNull;
 import lombok.val;
 
@@ -16,48 +18,63 @@ public abstract class I8nFacetAbstract
 extends FacetAbstract
 implements HasTranslation {
 
-    private final _Lazy<String> translated;
-    private final String originalText;
-    private final TranslationContext translationContext;
+    protected final TranslationContext translationContext;
+    @Getter(onMethod_ = {@Override}) private final ImmutableEnumSet<NounForm> supportedNounForms;
+
+    private final @NonNull NounForms nounForms;
+    private final @NonNull _Lazy<NounForms> translatedNounForms;
 
     protected I8nFacetAbstract(
             final Class<? extends Facet> facetType,
-            final String originalText,
+            final NounForms nounForms,
             final FacetHolder holder) {
-        this(facetType, originalText, holder, Precedence.DEFAULT);
+        this(facetType, nounForms, holder, Precedence.DEFAULT);
     }
 
     protected I8nFacetAbstract(
             final Class<? extends Facet> facetType,
-            final String originalText,
+            final NounForms nounForms,
             final FacetHolder holder,
             final Precedence precedence) {
         super(facetType, holder, precedence);
-        this.originalText = originalText;
+        this.nounForms = nounForms;
+        this.supportedNounForms = ImmutableEnumSet.from(nounForms.getSupportedNounForms());
         this.translationContext = TranslationContext
                 .forTranslationContextHolder(holder.getFeatureIdentifier());
-        this.translated = _Lazy.threadSafe(()->
-                holder
-                    .getTranslationService()
-                    .translate(translationContext, originalText));
+        this.translatedNounForms = _Lazy.threadSafe(()->
+            nounForms.translate(holder.getTranslationService(), translationContext));
+    }
+
+    @Override
+    public final String preferredText() {
+        return text(nounForms.getPreferredNounForm());
+    }
+
+    @Override
+    public final String preferredTranslated() {
+        return translated(nounForms.getPreferredNounForm());
     }
 
     @Override
-    public String text() {
-        return originalText;
+    public final String text(final @NonNull NounForm nounForm) {
+        return nounForms.get(nounForm);
     }
 
     @Override
-    public String translated() {
-        return translated.get();
+    public final String translated(final NounForm nounForm) {
+        return translatedNounForms.get().get(nounForm);
     }
 
+
     @Override
     public void visitAttributes(final BiConsumer<String, Object> visitor) {
         super.visitAttributes(visitor);
         visitor.accept("context", translationContext);
-        visitor.accept("originalText", text());
-        visitor.accept("translated", translated()); // memoizes as a side-effect
+        visitor.accept("supportedNounForms", supportedNounForms);
+        supportedNounForms.forEach(nounForm->{
+            visitor.accept("originalText." + nounForm, text(nounForm));
+            visitor.accept("translated." + nounForm, translated(nounForm)); // memoizes as a side-effect
+        });
     }
 
     @Override
@@ -65,14 +82,25 @@ implements HasTranslation {
 
         // equality by facet-type, text and context
 
-        if(! this.facetType().equals(other.facetType())) {
+        if(!this.facetType().equals(other.facetType())) {
             return false;
         }
 
         val otherFacet =  (I8nFacetAbstract)other;
 
-        return Objects.equals(this.text(), otherFacet.text())
-                && Objects.equals(this.translationContext, otherFacet.translationContext);
+        if(!Objects.equals(this.supportedNounForms, otherFacet.supportedNounForms)
+                || !Objects.equals(this.translationContext, otherFacet.translationContext)) {
+            return false;
+        }
+
+        for(val nounForm : supportedNounForms) {
+            if(!Objects.equals(this.text(nounForm), otherFacet.text(nounForm))){
+                return false;
+            }
+        }
+
+        return true;
+
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/standalonelist/NamedFacetOnStandaloneList.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/NounForm.java
similarity index 66%
rename from core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/standalonelist/NamedFacetOnStandaloneList.java
rename to core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/NounForm.java
index 1c22da4..cdee2e5 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/standalonelist/NamedFacetOnStandaloneList.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/NounForm.java
@@ -16,16 +16,30 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
+package org.apache.isis.core.metamodel.facets.all.i8n;
 
-package org.apache.isis.core.metamodel.specloader.specimpl.standalonelist;
+public enum NounForm {
 
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
-import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
+    INDIFFERENT,
+    EMPTY,
+    SINGULAR,
+    PLURAL
+    ;
 
-public class NamedFacetOnStandaloneList extends NamedFacetAbstract {
+    public boolean isIndifferent() {
+        return this == INDIFFERENT;
+    }
+
+    public boolean isEmpty() {
+        return this == EMPTY;
+    }
+
+    public boolean isSingular() {
+        return this == SINGULAR;
+    }
 
-    public NamedFacetOnStandaloneList(final String value, final FacetHolder holder) {
-        super(value, /*escaped*/ true, holder);
+    public boolean isPlural() {
+        return this == PLURAL;
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/NounForms.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/NounForms.java
new file mode 100644
index 0000000..e137cc2
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/NounForms.java
@@ -0,0 +1,134 @@
+/*
+ *  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.i8n;
+
+import java.util.EnumSet;
+
+import javax.annotation.Nullable;
+
+import org.apache.isis.applib.services.i18n.TranslationContext;
+import org.apache.isis.applib.services.i18n.TranslationService;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.Value;
+import lombok.val;
+
+@Value @Builder
+public class NounForms {
+
+    public static NounFormsBuilder preferredSingular() {
+        return NounForms.builder()
+                .preferredNounForm(NounForm.SINGULAR);
+    }
+
+    public static NounFormsBuilder preferredPlural() {
+        return NounForms.builder()
+                .preferredNounForm(NounForm.PLURAL);
+    }
+
+    private final @Nullable String indifferent;
+    private final @Nullable String empty;
+    private final @Nullable String singular;
+    private final @Nullable String plural;
+
+    private final @NonNull NounForm preferredNounForm;
+
+    @Getter(lazy = true)
+    final EnumSet<NounForm> supportedNounForms = supportedNounForms();
+
+    private EnumSet<NounForm> supportedNounForms() {
+
+        val supportedNounForms = EnumSet.noneOf(NounForm.class);
+
+        if(indifferent!=null) {
+            supportedNounForms.add(NounForm.INDIFFERENT);
+        }
+
+        if(empty!=null) {
+            supportedNounForms.add(NounForm.EMPTY);
+        }
+
+        if(singular!=null) {
+            supportedNounForms.add(NounForm.SINGULAR);
+        }
+
+        if(plural!=null) {
+            supportedNounForms.add(NounForm.PLURAL);
+        }
+
+        return supportedNounForms;
+    }
+
+    public String get(final @NonNull NounForm nounForm) {
+        if(!getSupportedNounForms().contains(nounForm)) {
+            throw _Exceptions.illegalArgument("NounForm %s not supported with this instance", nounForm);
+        };
+        switch(nounForm) {
+        case INDIFFERENT:
+            return getIndifferent();
+        case EMPTY:
+            return getEmpty();
+        case SINGULAR:
+            return getSingular();
+        case PLURAL:
+            return getPlural();
+        default:
+            break;
+        }
+        throw _Exceptions.unmatchedCase(nounForm);
+    }
+
+    public NounForms translate(
+            final @NonNull TranslationService translationService,
+            final TranslationContext context) {
+
+        val builder = NounForms
+                .builder()
+                .preferredNounForm(preferredNounForm);
+
+
+        getSupportedNounForms()
+        .forEach(nounForm->{
+
+            switch(nounForm) {
+            case INDIFFERENT:
+                builder.indifferent(translationService.translate(context, indifferent));
+                break;
+            case EMPTY:
+                builder.empty(translationService.translate(context, empty));
+                break;
+            case SINGULAR:
+                builder.singular(translationService.translate(context, singular));
+                break;
+            case PLURAL:
+                builder.plural(translationService.translate(context, plural));
+                break;
+            default:
+                throw _Exceptions.unmatchedCase(nounForm);
+            }
+
+        });
+
+        return builder.build();
+    }
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacet.java
index cdee267..3cced9d 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacet.java
@@ -29,7 +29,10 @@ import org.apache.isis.core.metamodel.facets.all.i8n.HasTranslation;
  * In the standard Apache Isis Programming Model, corresponds to annotating the
  * member with <tt>@Named</tt>.
  */
-public interface NamedFacet extends Facet, HasTranslation {
+public interface NamedFacet
+extends
+    Facet,
+    HasTranslation {
 
     /**
      * Flag indicating whether the label should be show as is, or should be HTML escaped.
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacetAbstract.java
index bf9f501..7dbd9af 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacetAbstract.java
@@ -24,6 +24,7 @@ import java.util.function.BiConsumer;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.all.i8n.I8nFacetAbstract;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
 
 import lombok.NonNull;
 import lombok.val;
@@ -38,12 +39,19 @@ implements NamedFacet {
 
     private final boolean escaped;
 
-    protected NamedFacetAbstract(String originalText, boolean escaped, FacetHolder holder) {
-        this(originalText, escaped, holder, Precedence.DEFAULT);
+    protected NamedFacetAbstract(
+            final NounForms namedNounForms,
+            final boolean escaped,
+            final FacetHolder holder) {
+        this(namedNounForms, escaped, holder, Precedence.DEFAULT);
     }
 
-    protected NamedFacetAbstract(String originalText, boolean escaped, FacetHolder holder, final Facet.Precedence precedence) {
-        super(type(), originalText, holder, precedence);
+    protected NamedFacetAbstract(
+            final NounForms namedNounForms,
+            final boolean escaped,
+            final FacetHolder holder,
+            final Facet.Precedence precedence) {
+        super(type(), namedNounForms, holder, precedence);
         this.escaped = escaped;
     }
 
@@ -64,7 +72,7 @@ implements NamedFacet {
             return false;
         }
 
-        val otherNamedFacet =  (NamedFacetAbstract)other;
+        val otherNamedFacet = (NamedFacetAbstract)other;
 
         return this.escaped() == otherNamedFacet.escaped()
                 && super.semanticEquals(otherNamedFacet);
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacetForMemberName.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacetForMemberName.java
index 3acbdc9..5153409 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacetForMemberName.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacetForMemberName.java
@@ -20,12 +20,18 @@
 package org.apache.isis.core.metamodel.facets.all.named;
 
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
+
+import lombok.NonNull;
 
 public class NamedFacetForMemberName
 extends NamedFacetFromMemberNameAbstract {
 
-    public NamedFacetForMemberName(String memberName, FacetHolder holder) {
-        super(memberName, holder, Precedence.DEFAULT);
+    public NamedFacetForMemberName(
+            final @NonNull NounForm preferredNounForm,
+            final String memberName,
+            final FacetHolder holder) {
+        super(preferredNounForm, memberName, holder, Precedence.DEFAULT);
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacetFromMemberNameAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacetFromMemberNameAbstract.java
index cd0b8a2..9b896db 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacetFromMemberNameAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacetFromMemberNameAbstract.java
@@ -21,6 +21,10 @@ package org.apache.isis.core.metamodel.facets.all.named;
 
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
+
+import lombok.NonNull;
 
 public abstract class NamedFacetFromMemberNameAbstract
 extends NamedFacetAbstract {
@@ -28,11 +32,23 @@ extends NamedFacetAbstract {
     public static final boolean ESCAPED = true;
 
     protected NamedFacetFromMemberNameAbstract(
-            final String name,
+            final @NonNull NounForm preferredNounForm,
+            final String memberName,
             final FacetHolder holder,
             final Facet.Precedence precedence) {
         super(
-                name,
+                preferredNounForm.isSingular()
+                ? NounForms
+                        .builder()
+                        .preferredNounForm(preferredNounForm)
+                        .singular(memberName)
+                        .build()
+                : NounForms
+                        .builder()
+                        .preferredNounForm(preferredNounForm)
+                        .plural(memberName)
+                        .build()
+                ,
                 ESCAPED,
                 holder,
                 precedence);
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/layout/NamedFacetForCollectionLayoutAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/layout/NamedFacetForCollectionLayoutAnnotation.java
index 078e60f..8adca66 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/layout/NamedFacetForCollectionLayoutAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/layout/NamedFacetForCollectionLayoutAnnotation.java
@@ -24,6 +24,7 @@ import java.util.Optional;
 import org.apache.isis.applib.annotation.CollectionLayout;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
 
 public class NamedFacetForCollectionLayoutAnnotation extends NamedFacetAbstract {
@@ -40,11 +41,11 @@ public class NamedFacetForCollectionLayoutAnnotation extends NamedFacetAbstract
     }
 
     private NamedFacetForCollectionLayoutAnnotation(
-            final String value,
+            final String pluralName,
             final boolean escaped,
             final FacetHolder holder) {
 
-        super(value, escaped, holder);
+        super(NounForms.preferredPlural().plural(pluralName).build(), escaped, holder);
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/layout/NamedFacetForCollectionXml.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/layout/NamedFacetForCollectionXml.java
index 40c6085..82ae51f 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/layout/NamedFacetForCollectionXml.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/layout/NamedFacetForCollectionXml.java
@@ -24,6 +24,7 @@ import java.util.Optional;
 import org.apache.isis.applib.layout.component.CollectionLayoutData;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
 
@@ -44,11 +45,11 @@ extends NamedFacetAbstract {
     }
 
     private NamedFacetForCollectionXml(
-            final String value,
+            final String pluralName,
             final boolean escaped,
             final FacetHolder holder) {
 
-        super(value, escaped, holder);
+        super(NounForms.preferredPlural().plural(pluralName).build(), escaped, holder);
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/fallback/FallbackFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/fallback/FallbackFacetFactory.java
index 640d3ef..bb61a2b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/fallback/FallbackFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/fallback/FallbackFacetFactory.java
@@ -32,6 +32,7 @@ import org.apache.isis.core.metamodel.facetapi.FeatureType;
 import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
 import org.apache.isis.core.metamodel.facets.FacetedMethod;
 import org.apache.isis.core.metamodel.facets.TypedHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 
 import lombok.val;
 
@@ -85,7 +86,7 @@ public class FallbackFacetFactory extends FacetFactoryAbstract {
 
         final FacetedMethod facetedMethod = processMethodContext.getFacetHolder();
 
-        addFacet(new NamedFacetFallbackFromMemberName(facetedMethod));
+        addFacet(new NamedFacetFallbackFromMemberName(NounForm.SINGULAR, facetedMethod));
         addFacet(new DescribedAsFacetNone(facetedMethod));
         addFacet(new HelpFacetNone(facetedMethod));
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/fallback/NamedFacetFallbackFromMemberName.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/fallback/NamedFacetFallbackFromMemberName.java
index 66fe499..ad3ca2c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/fallback/NamedFacetFallbackFromMemberName.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/fallback/NamedFacetFallbackFromMemberName.java
@@ -21,13 +21,15 @@ package org.apache.isis.core.metamodel.facets.fallback;
 
 import org.apache.isis.core.metamodel.commons.StringExtensions;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetFromMemberNameAbstract;
 
 public class NamedFacetFallbackFromMemberName
 extends NamedFacetFromMemberNameAbstract {
 
-    protected NamedFacetFallbackFromMemberName(FacetHolder holder) {
+    protected NamedFacetFallbackFromMemberName(final NounForm nounForm, final FacetHolder holder) {
         super(
+                nounForm,
                 StringExtensions.asNaturalName2(holder.getFeatureIdentifier().getMemberLogicalName()),
                 holder,
                 Precedence.FALLBACK);
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/fallback/NamedFacetNone.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/fallback/NamedFacetNone.java
index efca58e..945bca8 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/fallback/NamedFacetNone.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/fallback/NamedFacetNone.java
@@ -20,6 +20,7 @@
 package org.apache.isis.core.metamodel.facets.fallback;
 
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
 
 /**
@@ -28,12 +29,13 @@ import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
  * <p>
  * TODO: should this instead be the empty string?
  */
-public class NamedFacetNone extends NamedFacetAbstract {
+public class NamedFacetNone
+extends NamedFacetAbstract {
 
     public static final boolean ESCAPED = true;
 
     public NamedFacetNone(final FacetHolder holder) {
-        super(null, ESCAPED, holder, Precedence.FALLBACK);
+        super(NounForms.preferredSingular().build(), ESCAPED, holder, Precedence.FALLBACK);
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/DomainObjectLayoutFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/DomainObjectLayoutFacetFactory.java
index 759b524..33f231c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/DomainObjectLayoutFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/DomainObjectLayoutFacetFactory.java
@@ -79,10 +79,6 @@ extends FacetFactoryAbstract {
                 .create(domainObjectLayoutIfAny, facetHolder));
 
         addFacetIfPresent(
-                PluralFacetForDomainObjectLayoutAnnotation
-                .create(domainObjectLayoutIfAny, facetHolder));
-
-        addFacetIfPresent(
                 BookmarkPolicyFacetForDomainObjectLayoutAnnotation
                 .create(domainObjectLayoutIfAny, facetHolder));
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/NamedFacetForDomainObjectLayoutAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/NamedFacetForDomainObjectLayoutAnnotation.java
index a70708a..1621f65 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/NamedFacetForDomainObjectLayoutAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/NamedFacetForDomainObjectLayoutAnnotation.java
@@ -24,22 +24,51 @@ import java.util.Optional;
 import org.apache.isis.applib.annotation.DomainObjectLayout;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
 
+import lombok.val;
 
-public class NamedFacetForDomainObjectLayoutAnnotation extends NamedFacetAbstract {
+
+public class NamedFacetForDomainObjectLayoutAnnotation
+extends NamedFacetAbstract {
 
     public static Optional<NamedFacetForDomainObjectLayoutAnnotation> create(
             final Optional<DomainObjectLayout> domainObjectLayoutIfAny,
             final FacetHolder holder) {
 
-        return domainObjectLayoutIfAny
-                .map(DomainObjectLayout::named)
-                .filter(_Strings::isNotEmpty)
-                .map(named -> new NamedFacetForDomainObjectLayoutAnnotation(named, holder));
+        if(!domainObjectLayoutIfAny.isPresent()) {
+            return Optional.empty();
+        }
+
+        val domainObjectLayout = domainObjectLayoutIfAny.get();
+
+        val singular = _Strings.emptyToNull(domainObjectLayout.named());
+        val plural = _Strings.emptyToNull(domainObjectLayout.plural());
+
+        val nounForms = NounForms
+                .builder()
+                .preferredNounForm(singular!=null ? NounForm.SINGULAR : NounForm.PLURAL)
+                .singular(singular)
+                .plural(plural)
+                .build();
+
+        if(nounForms.getSupportedNounForms().isEmpty()) {
+            return Optional.empty();
+        }
+
+        return Optional.of(
+                new NamedFacetForDomainObjectLayoutAnnotation(
+                            nounForms,
+                            holder));
+
     }
 
-    private NamedFacetForDomainObjectLayoutAnnotation(final String value, final FacetHolder holder) {
-        super(value, /*escaped*/ true, holder);
+    private NamedFacetForDomainObjectLayoutAnnotation(
+            final NounForms nounForms,
+            final FacetHolder holder) {
+        super(nounForms, /*escaped*/ true, holder);
     }
+
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/NamedFacetForDomainObjectXml.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/NamedFacetForDomainObjectXml.java
index 4200c77..8b4e651 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/NamedFacetForDomainObjectXml.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/NamedFacetForDomainObjectXml.java
@@ -24,30 +24,53 @@ import java.util.Optional;
 import org.apache.isis.applib.layout.component.DomainObjectLayoutData;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
 
+import lombok.val;
+
 public class NamedFacetForDomainObjectXml
 extends NamedFacetAbstract {
 
     public static Optional<NamedFacet> create(
             final DomainObjectLayoutData domainObjectLayout,
             final FacetHolder holder) {
+
         if(domainObjectLayout == null) {
             return Optional.empty();
         }
-        final String named = _Strings.emptyToNull(domainObjectLayout.getNamed());
-        Boolean escaped = domainObjectLayout.getNamedEscaped();
-        return named != null
-                ? Optional.of(new NamedFacetForDomainObjectXml(named, (escaped == null || escaped), holder))
-                : Optional.empty();
+
+        val singular = _Strings.emptyToNull(domainObjectLayout.getNamed());
+        val plural = _Strings.emptyToNull(domainObjectLayout.getPlural());
+
+        val nounForms = NounForms
+                .builder()
+                .preferredNounForm(singular!=null ? NounForm.SINGULAR : NounForm.PLURAL)
+                .singular(singular)
+                .plural(plural)
+                .build();
+
+        if(nounForms.getSupportedNounForms().isEmpty()) {
+            return Optional.empty();
+        }
+
+        final Boolean _escaped = domainObjectLayout.getNamedEscaped();
+        final boolean escaped = (_escaped == null || _escaped);
+
+        return Optional.of(
+                new NamedFacetForDomainObjectXml(
+                            nounForms,
+                            escaped,
+                            holder));
     }
 
     private NamedFacetForDomainObjectXml(
-            final String value,
+            final NounForms nounForms,
             final boolean escaped,
             final FacetHolder holder) {
-        super(value, escaped, holder);
+        super(nounForms, escaped, holder);
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/PluralFacetForDomainObjectLayoutAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/PluralFacetForDomainObjectLayoutAnnotation.java
deleted file mode 100644
index f557cb6..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/PluralFacetForDomainObjectLayoutAnnotation.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  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.object.domainobjectlayout;
-
-
-import java.util.Objects;
-import java.util.Optional;
-
-import org.apache.isis.applib.annotation.DomainObjectLayout;
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacetAbstract;
-
-
-public class PluralFacetForDomainObjectLayoutAnnotation extends PluralFacetAbstract {
-
-    public static Optional<PluralFacetForDomainObjectLayoutAnnotation> create(
-            final Optional<DomainObjectLayout> domainObjectLayoutIfAny,
-            final FacetHolder holder) {
-
-        return domainObjectLayoutIfAny
-                .map(DomainObjectLayout::plural)
-                .filter(Objects::nonNull)
-                .map(plural -> new PluralFacetForDomainObjectLayoutAnnotation(plural, holder));
-    }
-
-    private PluralFacetForDomainObjectLayoutAnnotation(final String value, final FacetHolder holder) {
-        super(value, holder);
-    }
-}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/PluralFacetForDomainObjectXml.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/PluralFacetForDomainObjectXml.java
deleted file mode 100644
index 6a4ac63..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/PluralFacetForDomainObjectXml.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- *  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.object.domainobjectlayout;
-
-import java.util.Optional;
-
-import org.apache.isis.applib.layout.component.DomainObjectLayoutData;
-import org.apache.isis.commons.internal.base._Strings;
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacetAbstract;
-
-public class PluralFacetForDomainObjectXml
-extends PluralFacetAbstract {
-
-    public static Optional<PluralFacet> create(
-            final DomainObjectLayoutData domainObjectLayout,
-            final FacetHolder holder) {
-        if(domainObjectLayout == null) {
-            return Optional.empty();
-        }
-        final String plural = _Strings.emptyToNull(domainObjectLayout.getPlural());
-        return plural != null
-                ? Optional.of(new PluralFacetForDomainObjectXml(plural, holder))
-                : Optional.empty();
-    }
-
-    private PluralFacetForDomainObjectXml(final String value, final FacetHolder holder) {
-        super(value, holder);
-    }
-}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainservicelayout/DomainServiceLayoutFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainservicelayout/DomainServiceLayoutFacetFactory.java
index 4b040ac5..2996b58 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainservicelayout/DomainServiceLayoutFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainservicelayout/DomainServiceLayoutFacetFactory.java
@@ -18,13 +18,10 @@
  */
 package org.apache.isis.core.metamodel.facets.object.domainservicelayout;
 
-import java.util.Objects;
-
 import javax.inject.Inject;
 
 import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.applib.annotation.DomainServiceLayout;
-import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
 import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
@@ -64,15 +61,9 @@ extends FacetFactoryAbstract {
         addFacet(
                 new DomainServiceLayoutFacetAnnotation(facetHolder, menuBar));
 
-        val named = domainServiceLayoutIfAny
-                .map(DomainServiceLayout::named)
-                .map(_Strings::emptyToNull)
-                .filter(Objects::nonNull)
-                .orElse(null);
-
         addFacetIfPresent(
                 NamedFacetForDomainServiceLayoutAnnotation
-                .create(named, facetHolder));
+                .create(domainServiceLayoutIfAny, facetHolder));
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainservicelayout/NamedFacetForDomainServiceLayoutAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainservicelayout/NamedFacetForDomainServiceLayoutAnnotation.java
index ea956d1..6785e0a 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainservicelayout/NamedFacetForDomainServiceLayoutAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainservicelayout/NamedFacetForDomainServiceLayoutAnnotation.java
@@ -18,23 +18,51 @@
  */
 package org.apache.isis.core.metamodel.facets.object.domainservicelayout;
 
+import java.util.Objects;
 import java.util.Optional;
 
+import org.apache.isis.applib.annotation.DomainServiceLayout;
+import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
 
+import lombok.val;
+
 public class NamedFacetForDomainServiceLayoutAnnotation
 extends NamedFacetAbstract {
 
     public static Optional<NamedFacet> create(
-            final String named,
-            final FacetHolder holder) {
-        return Optional.ofNullable(named)
-                .map(name->new NamedFacetForDomainServiceLayoutAnnotation(name, holder));
+            final Optional<DomainServiceLayout> domainServiceLayoutIfAny,
+            final FacetHolder facetHolder) {
+
+        val serviceNamed = domainServiceLayoutIfAny
+                .map(DomainServiceLayout::named)
+                .map(_Strings::emptyToNull)
+                .filter(Objects::nonNull)
+                .orElse(null);
+
+        if(_Strings.isEmpty(serviceNamed)) {
+            return Optional.empty();
+        }
+
+        val nounForms = NounForms
+                .preferredSingular()
+                .singular(serviceNamed)
+                .build();
+
+        return Optional.of(
+                new NamedFacetForDomainServiceLayoutAnnotation(
+                            nounForms,
+                            facetHolder));
     }
 
-    private NamedFacetForDomainServiceLayoutAnnotation(final String value, final FacetHolder holder) {
-        super(value, /*escaped*/ true, holder);
+    private NamedFacetForDomainServiceLayoutAnnotation(
+            final NounForms nounForms,
+            final FacetHolder holder) {
+        super(nounForms, /*escaped*/ true, holder);
     }
+
+
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/icon/IconFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/icon/IconFacet.java
index d64d0d9..ae98c69 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/icon/IconFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/icon/IconFacet.java
@@ -20,7 +20,6 @@
 package org.apache.isis.core.metamodel.facets.object.icon;
 
 import org.apache.isis.core.metamodel.facetapi.Facet;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
 import org.apache.isis.core.metamodel.facets.object.title.TitleFacet;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 
@@ -38,7 +37,6 @@ import org.apache.isis.core.metamodel.spec.ManagedObject;
  * In the standard Apache Isis Programming Model, typically corresponds to a method named {@code iconName}.
  *
  * @see TitleFacet
- * @see PluralFacet
  */
 public interface IconFacet extends Facet {
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/plural/PluralFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/plural/PluralFacet.java
deleted file mode 100644
index 71e84e6..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/plural/PluralFacet.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- *  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.object.plural;
-
-import org.apache.isis.core.metamodel.facetapi.Facet;
-import org.apache.isis.core.metamodel.facets.all.i8n.HasTranslation;
-import org.apache.isis.core.metamodel.facets.object.icon.IconFacet;
-import org.apache.isis.core.metamodel.facets.object.title.TitleFacet;
-
-/**
- * Mechanism for obtaining the plural title of an instance of a class, used to
- * label a collection of a certain class.
- *
- * <p>
- * In the standard Apache Isis Programming Model, typically corresponds to a
- * method named <tt>pluralName</tt>. If no plural name is provided, then the
- * framework will attempt to guess the plural name (by adding an <i>s</i> or
- * <i>ies</i> suffix).
- *
- * @see IconFacet
- * @see TitleFacet
- */
-public interface PluralFacet extends Facet, HasTranslation {
-
-    /**
-     * Originating text to be translated before use with the UI.
-     */
-    String text();
-
-    String translated();
-
-}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/plural/PluralFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/plural/PluralFacetAbstract.java
deleted file mode 100644
index 21380fe..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/plural/PluralFacetAbstract.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- *  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.object.plural;
-
-import org.apache.isis.core.metamodel.facetapi.Facet;
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
-import org.apache.isis.core.metamodel.facets.all.i8n.I8nFacetAbstract;
-
-public abstract class PluralFacetAbstract
-extends I8nFacetAbstract
-implements PluralFacet {
-
-    private static final Class<? extends Facet> type() {
-        return PluralFacet.class;
-    }
-
-    protected PluralFacetAbstract(String originalText, FacetHolder holder) {
-        this(originalText, holder, Precedence.DEFAULT);
-    }
-
-    protected PluralFacetAbstract(String originalText, FacetHolder holder, final Facet.Precedence precedence) {
-        super(type(), originalText, holder, precedence);
-    }
-
-
-}
-
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/plural/inferred/PluralFacetInferred.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/plural/inferred/PluralFacetInferred.java
deleted file mode 100644
index 1dbd700..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/plural/inferred/PluralFacetInferred.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- *  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.object.plural.inferred;
-
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacetAbstract;
-
-public class PluralFacetInferred
-extends PluralFacetAbstract {
-
-    public PluralFacetInferred(final String value, final FacetHolder holder) {
-        super(value, holder, Precedence.INFERRED);
-    }
-
-}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/TitleFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/TitleFacet.java
index e84e9e9..9f7b25b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/TitleFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/TitleFacet.java
@@ -23,7 +23,6 @@ import java.util.function.Predicate;
 
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facets.object.icon.IconFacet;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 
 /**
@@ -35,7 +34,6 @@ import org.apache.isis.core.metamodel.spec.ManagedObject;
  * method named <tt>title</tt>.
  *
  * @see IconFacet
- * @see PluralFacet
  */
 public interface TitleFacet extends Facet {
 
@@ -52,7 +50,9 @@ public interface TitleFacet extends Facet {
      * context object would be the parent.  Thus, the title might be shortened so that it does not
      * needlessly incorporate the title of the parent (context) object.
      */
-    default String title(Predicate<ManagedObject> isContextAdapter, ManagedObject targetAdapter) {
+    default String title(
+            final Predicate<ManagedObject> isContextAdapter,
+            final ManagedObject targetAdapter) {
         //Default implementation that simply delegates to {@link TitleFacet#title(ManagedObject)}.
         return title(targetAdapter);
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mandatory/MandatoryFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mandatory/MandatoryFacetAbstract.java
index e465c04..7094632 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mandatory/MandatoryFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mandatory/MandatoryFacetAbstract.java
@@ -25,6 +25,7 @@ import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetAbstract;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.interactions.ActionArgValidityContext;
 import org.apache.isis.core.metamodel.interactions.PropertyModifyContext;
@@ -104,7 +105,7 @@ implements MandatoryFacet {
         }
         final NamedFacet namedFacet = getFacetHolder().getFacet(NamedFacet.class);
         final String name = namedFacet != null
-                ? namedFacet.translated()
+                ? namedFacet.translated(NounForm.SINGULAR)
                 : null;
         return name != null
                 ? "'" + name + "' is mandatory"
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/layout/NamedFacetForParameterLayoutAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/layout/NamedFacetForParameterLayoutAnnotation.java
index 06687d1..055b33d 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/layout/NamedFacetForParameterLayoutAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/layout/NamedFacetForParameterLayoutAnnotation.java
@@ -24,27 +24,32 @@ import java.util.Optional;
 import org.apache.isis.applib.annotation.ParameterLayout;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
 
-public class NamedFacetForParameterLayoutAnnotation extends NamedFacetAbstract {
+public class NamedFacetForParameterLayoutAnnotation
+extends NamedFacetAbstract {
 
     public static Optional<NamedFacet> create(
             final Optional<ParameterLayout> parameterLayoutIfAny,
             final FacetHolder holder) {
 
         return parameterLayoutIfAny
-                .filter(parameterLayout -> _Strings.emptyToNull(parameterLayout.named()) != null)
-                .map(parameterLayout -> new NamedFacetForParameterLayoutAnnotation(
-                        parameterLayout.named(), parameterLayout.namedEscaped(), holder));
+                .filter(parameterLayout -> _Strings.isNotEmpty(parameterLayout.named()))
+                .map(parameterLayout ->
+                        new NamedFacetForParameterLayoutAnnotation(
+                            parameterLayout.named(),
+                            parameterLayout.namedEscaped(),
+                            holder));
     }
 
     private NamedFacetForParameterLayoutAnnotation(
-            final String value,
+            final String singularName,
             final boolean escaped,
             final FacetHolder holder) {
 
-        super(value, escaped, holder);
+        super(NounForms.preferredSingular().singular(singularName).build(), escaped, holder);
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/name/NamedFacetForParameterUsingReflection.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/name/NamedFacetForParameterUsingReflection.java
index fbc41a3..622769e 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/name/NamedFacetForParameterUsingReflection.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/name/NamedFacetForParameterUsingReflection.java
@@ -19,6 +19,7 @@
 package org.apache.isis.core.metamodel.facets.param.name;
 
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
 
 /**
@@ -28,8 +29,8 @@ import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
 public class NamedFacetForParameterUsingReflection
 extends NamedFacetAbstract {
 
-    public NamedFacetForParameterUsingReflection(final String value, final FacetHolder holder) {
-        super(value, false, holder);
+    public NamedFacetForParameterUsingReflection(final String singularName, final FacetHolder holder) {
+        super(NounForms.preferredSingular().singular(singularName).build(), false, holder);
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/NamedFacetForPropertyLayoutAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/NamedFacetForPropertyLayoutAnnotation.java
index 685536f..fb3d42d 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/NamedFacetForPropertyLayoutAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/NamedFacetForPropertyLayoutAnnotation.java
@@ -24,9 +24,11 @@ import java.util.Optional;
 import org.apache.isis.applib.annotation.PropertyLayout;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
 
-public class NamedFacetForPropertyLayoutAnnotation extends NamedFacetAbstract {
+public class NamedFacetForPropertyLayoutAnnotation
+extends NamedFacetAbstract {
 
     public static Optional<NamedFacetForPropertyLayoutAnnotation> create(
             final Optional<PropertyLayout> propertyLayoutIfAny,
@@ -34,16 +36,19 @@ public class NamedFacetForPropertyLayoutAnnotation extends NamedFacetAbstract {
 
         return propertyLayoutIfAny
                 .filter(propertyLayout->_Strings.isNotEmpty(propertyLayout.named()))
-                .map(propertyLayout -> new NamedFacetForPropertyLayoutAnnotation(
-                        propertyLayout.named(), propertyLayout.namedEscaped(), holder));
+                .map(propertyLayout ->
+                    new NamedFacetForPropertyLayoutAnnotation(
+                        propertyLayout.named(),
+                        propertyLayout.namedEscaped(),
+                        holder));
     }
 
     private NamedFacetForPropertyLayoutAnnotation(
-            final String value,
+            final String singularName,
             final boolean escaped,
             final FacetHolder holder) {
 
-        super(value, escaped, holder);
+        super(NounForms.preferredSingular().singular(singularName).build(), escaped, holder);
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/NamedFacetForPropertyXml.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/NamedFacetForPropertyXml.java
index b505a25..ef5df97 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/NamedFacetForPropertyXml.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/NamedFacetForPropertyXml.java
@@ -24,30 +24,47 @@ import java.util.Optional;
 import org.apache.isis.applib.layout.component.PropertyLayoutData;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
 
-public class NamedFacetForPropertyXml extends NamedFacetAbstract {
+import lombok.val;
+
+public class NamedFacetForPropertyXml
+extends NamedFacetAbstract {
 
     public static Optional<NamedFacet> create(
             final PropertyLayoutData propertyLayout,
             final FacetHolder holder) {
+
         if(propertyLayout == null) {
             return Optional.empty();
         }
-        final String named = _Strings.emptyToNull(propertyLayout.getNamed());
-        final Boolean escaped = propertyLayout.getNamedEscaped();
-        return named != null
-                ? Optional.of(new NamedFacetForPropertyXml(named, (escaped == null || escaped), holder))
-                : Optional.empty();
+
+        val nounForms = NounForms
+                .preferredSingular()
+                .singular(_Strings.emptyToNull(propertyLayout.getNamed()))
+                .build();
+
+        if(nounForms.getSupportedNounForms().isEmpty()) {
+            return Optional.empty();
+        }
+
+        final Boolean _escaped = propertyLayout.getNamedEscaped();
+        final boolean escaped = (_escaped == null || _escaped);
+
+        return Optional.of(
+                new NamedFacetForPropertyXml(
+                            nounForms,
+                            escaped,
+                            holder));
     }
 
     private NamedFacetForPropertyXml(
-            final String value,
+            final NounForms nounForms,
             final boolean escaped,
             final FacetHolder holder) {
-
-        super(value, escaped, holder);
+        super(nounForms, escaped, holder);
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/layout/LayoutFacetUtil.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/layout/LayoutFacetUtil.java
index c7ce045..38841a6 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/layout/LayoutFacetUtil.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/layout/LayoutFacetUtil.java
@@ -42,6 +42,7 @@ import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.actions.position.ActionPositionFacet;
 import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
 import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.collections.collection.defaultview.DefaultViewFacet;
 import org.apache.isis.core.metamodel.facets.collections.sortedby.SortedByFacet;
@@ -49,7 +50,6 @@ import org.apache.isis.core.metamodel.facets.members.cssclass.CssClassFacet;
 import org.apache.isis.core.metamodel.facets.members.cssclassfa.CssClassFaFacet;
 import org.apache.isis.core.metamodel.facets.object.bookmarkpolicy.BookmarkPolicyFacet;
 import org.apache.isis.core.metamodel.facets.object.paged.PagedFacet;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
 import org.apache.isis.core.metamodel.facets.objectvalue.labelat.LabelAtFacet;
 import org.apache.isis.core.metamodel.facets.objectvalue.multiline.MultiLineFacet;
 import org.apache.isis.core.metamodel.facets.objectvalue.renderedadjusted.RenderedAdjustedFacet;
@@ -183,9 +183,10 @@ public class LayoutFacetUtil {
             final HasNamed hasNamed,
             final FacetHolder facetHolder) {
 
-        val namedFacet = facetHolder.getFacet(NamedFacet.class);
-        if(isDoOp(namedFacet)) {
-            final String named = namedFacet.translated();
+        facetHolder.lookupNonFallbackFacet(NamedFacet.class)
+        .filter(namedFacet->namedFacet.getSupportedNounForms().contains(NounForm.SINGULAR))
+        .ifPresent(namedFacet->{
+            final String named = namedFacet.translated(NounForm.SINGULAR);
             if(!_Strings.isNullOrEmpty(named)){
                 hasNamed.setNamed(named);
             }
@@ -193,7 +194,7 @@ public class LayoutFacetUtil {
             if(!escaped) {
                 hasNamed.setNamedEscaped(escaped);
             }
-        }
+        });
     }
 
     public void setPagedIfAny(
@@ -213,16 +214,16 @@ public class LayoutFacetUtil {
             final DomainObjectLayoutData domainObjectLayoutData,
             final FacetHolder facetHolder) {
 
-        val pluralFacet = facetHolder.getFacet(PluralFacet.class);
-        if(isDoOp(pluralFacet)) {
-            final String plural = pluralFacet.translated();
+        facetHolder.lookupNonFallbackFacet(NamedFacet.class)
+        .filter(namedFacet->namedFacet.getSupportedNounForms().contains(NounForm.PLURAL))
+        .ifPresent(namedFacet->{
+            val plural = namedFacet.translated(NounForm.PLURAL);
             if(!_Strings.isNullOrEmpty(plural)) {
                 domainObjectLayoutData.setPlural(plural);
             }
-        }
+        });
     }
 
-
     public void setActionPositionIfAny(
             final ActionLayoutData actionLayoutData,
             final FacetHolder facetHolder) {
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/postprocessors/all/i18n/TranslationPostProcessor.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/postprocessors/all/i18n/TranslationPostProcessor.java
index 0f5dfe0..2754a59 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/postprocessors/all/i18n/TranslationPostProcessor.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/postprocessors/all/i18n/TranslationPostProcessor.java
@@ -25,7 +25,6 @@ import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 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.facets.object.plural.PluralFacet;
 import org.apache.isis.core.metamodel.postprocessors.ObjectSpecificationPostProcessorAbstract;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
@@ -78,15 +77,12 @@ extends ObjectSpecificationPostProcessorAbstract {
     private void memoizeTranslations(final FacetHolder facetHolder) {
         facetHolder
             .lookupFacet(NamedFacet.class)
-            .ifPresent(NamedFacet::translated); // memoize translation
+            .ifPresent(NamedFacet::memoizeTranslations);
 
         facetHolder
             .lookupFacet(DescribedAsFacet.class)
-            .ifPresent(DescribedAsFacet::translated); // memoize translation
+            .ifPresent(DescribedAsFacet::memoizeTranslations);
 
-        facetHolder
-            .lookupFacet(PluralFacet.class)
-            .ifPresent(PluralFacet::translated); // memoize translation
     }
 
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridSystemServiceAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridSystemServiceAbstract.java
index 55d8d5d..c83766b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridSystemServiceAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridSystemServiceAbstract.java
@@ -65,7 +65,6 @@ import org.apache.isis.core.metamodel.facets.object.domainobjectlayout.CssClassF
 import org.apache.isis.core.metamodel.facets.object.domainobjectlayout.CssClassFacetForDomainObjectXml;
 import org.apache.isis.core.metamodel.facets.object.domainobjectlayout.DescribedAsFacetForDomainObjectXml;
 import org.apache.isis.core.metamodel.facets.object.domainobjectlayout.NamedFacetForDomainObjectXml;
-import org.apache.isis.core.metamodel.facets.object.domainobjectlayout.PluralFacetForDomainObjectXml;
 import org.apache.isis.core.metamodel.facets.properties.propertylayout.CssClassFacetForPropertyXml;
 import org.apache.isis.core.metamodel.facets.properties.propertylayout.DescribedAsFacetForPropertyXml;
 import org.apache.isis.core.metamodel.facets.properties.propertylayout.HiddenFacetForPropertyXml;
@@ -197,7 +196,6 @@ implements GridSystemService<G> {
                 addFacetIfPresent(CssClassFaFacetForDomainObjectXml.create(domainObjectLayoutData, objectSpec));
                 addFacetIfPresent(DescribedAsFacetForDomainObjectXml.create(domainObjectLayoutData, objectSpec));
                 addFacetIfPresent(NamedFacetForDomainObjectXml.create(domainObjectLayoutData, objectSpec));
-                addFacetIfPresent(PluralFacetForDomainObjectXml.create(domainObjectLayoutData, objectSpec));
             }
 
             @Override
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
index f2341e5..0fe4c50 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
@@ -46,6 +46,7 @@ import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
 import org.apache.isis.core.metamodel.facets.all.help.HelpFacet;
 import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.collections.CollectionFacet;
 import org.apache.isis.core.metamodel.facets.members.cssclass.CssClassFacet;
@@ -55,7 +56,6 @@ import org.apache.isis.core.metamodel.facets.object.icon.IconFacet;
 import org.apache.isis.core.metamodel.facets.object.immutable.ImmutableFacet;
 import org.apache.isis.core.metamodel.facets.object.parented.ParentedCollectionFacet;
 import org.apache.isis.core.metamodel.facets.object.parseable.ParseableFacet;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
 import org.apache.isis.core.metamodel.facets.object.title.TitleFacet;
 import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
 import org.apache.isis.core.metamodel.facets.object.viewmodel.ViewModelFacet;
@@ -167,7 +167,7 @@ extends
      * Returns the (singular) name for objects of this specification.
      *
      * <p>
-     * Corresponds to the {@link NamedFacet#value()} of {@link NamedFacet}; is
+     * Corresponds to the {@link NamedFacet#translated(NounForm)} of {@link NamedFacet}; is
      * not necessarily immutable.
      */
     String getSingularName();
@@ -185,7 +185,7 @@ extends
      * Returns the description, if any, of the specification.
      *
      * <p>
-     * Corresponds to the {@link DescribedAsFacet#value() value} of
+     * Corresponds to the {@link DescribedAsFacet#translated() value} of
      * {@link DescribedAsFacet}; is not necessarily immutable.
      */
     @Override
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
index 0ab595f..9c522b3 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
@@ -42,7 +42,6 @@ import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.isis.core.metamodel.consent.InteractionResultSet;
 import org.apache.isis.core.metamodel.facets.actions.action.associateWith.ChoicesFromFacet;
 import org.apache.isis.core.metamodel.facets.actions.position.ActionPositionFacet;
-import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.members.cssclass.CssClassFacet;
 import org.apache.isis.core.metamodel.facets.members.cssclassfa.CssClassFaFacet;
 import org.apache.isis.core.metamodel.facets.members.layout.group.LayoutGroupFacet;
@@ -257,7 +256,7 @@ public interface ObjectAction extends ObjectMember {
             final ManagedObject target,
             final InteractionInitiatedBy interactionInitiatedBy);
 
-    default String getCssClass(String prefix) {
+    default String getCssClass(final String prefix) {
         final String ownerId = getOnType().getLogicalTypeName().replace(".", "-");
         return prefix + ownerId + "-" + getId();
     }
@@ -273,19 +272,7 @@ public interface ObjectAction extends ObjectMember {
 
     public static final class Util {
 
-        public static String nameFor(final ObjectAction objAction) {
-            final String actionName = objAction.getName();
-            if (actionName != null) {
-                return actionName;
-            }
-            final NamedFacet namedFacet = objAction.getFacet(NamedFacet.class);
-            if (namedFacet != null) {
-                return namedFacet.translated();
-            }
-            return "(no name)";
-        }
-
-        public static SemanticsOf semanticsOf(final ObjectAction objectAction) {
+        private static SemanticsOf semanticsOf(final ObjectAction objectAction) {
             return objectAction.getSemantics();
         }
 
@@ -293,12 +280,12 @@ public interface ObjectAction extends ObjectMember {
             return semanticsOf(objectAction).isAreYouSure();
         }
 
-        public static boolean isIdempotentOrCachable(ObjectAction objectAction) {
+        public static boolean isIdempotentOrCachable(final ObjectAction objectAction) {
             final SemanticsOf semantics = semanticsOf(objectAction);
             return semantics.isIdempotentInNature() || semantics.isSafeAndRequestCacheable();
         }
 
-        public static boolean isNoParameters(ObjectAction objectAction) {
+        public static boolean isNoParameters(final ObjectAction objectAction) {
             return objectAction.getParameterCount()==0;
         }
 
@@ -322,11 +309,11 @@ public interface ObjectAction extends ObjectMember {
             return className + "-" + actionId;
         }
 
-        public static String descriptionOf(ObjectAction action) {
+        public static String descriptionOf(final ObjectAction action) {
             return action.getDescription();
         }
 
-        public static ActionLayout.Position actionLayoutPositionOf(ObjectAction action) {
+        public static ActionLayout.Position actionLayoutPositionOf(final ObjectAction action) {
             final ActionPositionFacet layoutFacet = action.getFacet(ActionPositionFacet.class);
             return layoutFacet != null ? layoutFacet.position() : ActionLayout.Position.BELOW;
         }
@@ -406,7 +393,7 @@ public interface ObjectAction extends ObjectMember {
     public static final class Predicates {
 
         public static Predicate<ObjectAction> ofActionType(final ActionType type) {
-            return (ObjectAction oa) -> oa.getType() == type;
+            return (final ObjectAction oa) -> oa.getType() == type;
         }
 
         public static Predicate<ObjectAction> isSameLayoutGroupAs(
@@ -414,7 +401,7 @@ public interface ObjectAction extends ObjectMember {
 
             final String assocIdLower = association.getId();
 
-            return (ObjectAction objectAction) -> {
+            return (final ObjectAction objectAction) -> {
 
                 val layoutGroupFacet = objectAction.getFacet(LayoutGroupFacet.class);
                 if (layoutGroupFacet == null) {
@@ -435,7 +422,7 @@ public interface ObjectAction extends ObjectMember {
                     .map(ObjectAssociation::getId)
                     .collect(Collectors.toCollection(HashSet::new));
 
-            return (ObjectAction objectAction) -> {
+            return (final ObjectAction objectAction) -> {
 
                 val layoutGroupFacet = objectAction.getFacet(LayoutGroupFacet.class);
                 if (layoutGroupFacet == null) {
@@ -507,7 +494,7 @@ public interface ObjectAction extends ObjectMember {
                 final InteractionInitiatedBy interactionInitiatedBy,
                 final Where where) {
 
-            return (ObjectAction objectAction) -> {
+            return (final ObjectAction objectAction) -> {
                 final Consent visible = objectAction.isVisible(target, interactionInitiatedBy, where);
                 return visible.isAllowed();
             };
@@ -518,7 +505,7 @@ public interface ObjectAction extends ObjectMember {
         }
 
         private static Predicate<ObjectAction> isWizard(final ObjectSpecification objectSpecification) {
-            return (ObjectAction input) -> {
+            return (final ObjectAction input) -> {
                 if (objectSpecification == null) {
                     return false;
                 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java
index e9039e6..1087365 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java
@@ -27,6 +27,7 @@ import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FacetHolderAbstract;
 import org.apache.isis.core.metamodel.facetapi.FacetUtil;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetForMemberName;
 import org.apache.isis.core.metamodel.interactions.InteractionHead;
@@ -95,7 +96,7 @@ implements MixedInMember {
 
         if(!isExplicitlyNamed) {
             val memberName = determineNameFrom(mixinAction);
-            this.addFacet(new NamedFacetForMemberName(memberName, facetHolder));
+            this.addFacet(new NamedFacetForMemberName(NounForm.SINGULAR, memberName, facetHolder));
         }
     }
 
@@ -127,7 +128,7 @@ implements MixedInMember {
     }
 
     @Override
-    public ActionInteractionHead interactionHead(@NonNull ManagedObject actionOwner) {
+    public ActionInteractionHead interactionHead(@NonNull final ManagedObject actionOwner) {
         return ActionInteractionHead.of(this, actionOwner, mixinAdapterFor(actionOwner));
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionParameterAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionParameterAbstract.java
index c2efbad..a30a486 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionParameterAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionParameterAbstract.java
@@ -37,6 +37,7 @@ import org.apache.isis.core.metamodel.facetapi.FeatureType;
 import org.apache.isis.core.metamodel.facetapi.HasFacetHolder;
 import org.apache.isis.core.metamodel.facets.TypedHolder;
 import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.param.autocomplete.ActionParameterAutoCompleteFacet;
 import org.apache.isis.core.metamodel.facets.param.autocomplete.MinLengthUtil;
@@ -134,8 +135,9 @@ implements ObjectActionParameter, HasFacetHolder {
     @Override
     public String getName() {
         final NamedFacet facet = getFacet(NamedFacet.class);
-        if (facet != null && facet.translated() != null) {
-            return facet.translated();
+        if (facet != null
+                && facet.translated(NounForm.SINGULAR) != null) {
+            return facet.translated(NounForm.SINGULAR);
         }
         val singularName = getSpecification().getSingularName();
         val parameters = getAction().getParameters(this::equalsShortIdentifier);
@@ -360,9 +362,9 @@ implements ObjectActionParameter, HasFacetHolder {
 
     @Override
     public Consent isValid(
-            InteractionHead head,
-            Can<ManagedObject> pendingArgs,
-            InteractionInitiatedBy interactionInitiatedBy) {
+            final InteractionHead head,
+            final Can<ManagedObject> pendingArgs,
+            final InteractionInitiatedBy interactionInitiatedBy) {
 
         val validityContext = createProposedArgumentInteractionContext(
                 head, pendingArgs, getNumber(), interactionInitiatedBy);
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberAbstract.java
index 7ff8c77..3c36c6e 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberAbstract.java
@@ -129,13 +129,13 @@ implements ObjectMember, HasMetaModelContext, HasFacetHolder {
             throw _Exceptions.unrecoverableFormatted("no namedFacet preset on %s", getFeatureIdentifier());
         }
 
-        return namedFacet.translated();
+        return namedFacet.preferredTranslated();
     }
 
     @Override
     public String getDescription() {
-        final DescribedAsFacet facet = getFacet(DescribedAsFacet.class);
-        return facet.translated();
+        val describedAsFacet = getFacet(DescribedAsFacet.class);
+        return describedAsFacet.translated();
     }
 
     @Override
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
index 4fbf874..baf2647 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
@@ -54,6 +54,7 @@ import org.apache.isis.core.metamodel.facetapi.FeatureType;
 import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
 import org.apache.isis.core.metamodel.facets.all.help.HelpFacet;
 import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.members.cssclass.CssClassFacet;
 import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
@@ -64,7 +65,6 @@ import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacet;
 import org.apache.isis.core.metamodel.facets.object.navparent.NavigableParentFacet;
 import org.apache.isis.core.metamodel.facets.object.parented.ParentedCollectionFacet;
 import org.apache.isis.core.metamodel.facets.object.parseable.ParseableFacet;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
 import org.apache.isis.core.metamodel.facets.object.title.TitleFacet;
 import org.apache.isis.core.metamodel.interactions.InteractionContext;
 import org.apache.isis.core.metamodel.interactions.InteractionUtils;
@@ -313,7 +313,7 @@ implements ObjectSpecification {
         }
     }
 
-    boolean isLessThan(IntrospectionState upTo) {
+    boolean isLessThan(final IntrospectionState upTo) {
         return this.introspectionState.compareTo(upTo) < 0;
     }
 
@@ -491,35 +491,34 @@ implements ObjectSpecification {
 
     // -- NAME, DESCRIPTION, PERSISTABILITY
 
-    /**
-     * The name according to any available {@link NamedFacet},
-     * but falling back to {@link #getFullIdentifier()} otherwise.
-     */
     @Override
     public String getSingularName() {
         val namedFacet = getFacet(NamedFacet.class);
-        return namedFacet != null? namedFacet.translated() : this.getFullIdentifier();
+        return namedFacet != null
+                && namedFacet.getSupportedNounForms().contains(NounForm.SINGULAR)
+                        ? namedFacet.translated(NounForm.SINGULAR)
+                        : this.getFullIdentifier();
     }
 
-    /**
-     * The pluralized name according to any available {@link PluralFacet},
-     * else <tt>null</tt>.
-     */
     @Override
     public String getPluralName() {
-        val pluralFacet = getFacet(PluralFacet.class);
-        return pluralFacet.translated();
+        val namedFacet = getFacet(NamedFacet.class);
+        return namedFacet != null
+                && namedFacet.getSupportedNounForms().contains(NounForm.PLURAL)
+                        ? namedFacet.translated(NounForm.PLURAL)
+                        : this.getFullIdentifier();
     }
 
     /**
-     * The description according to any available {@link PluralFacet},
+     * The translated description according to any available {@link DescribedAsFacet},
      * else empty string (<tt>""</tt>).
      */
     @Override
     public String getDescription() {
         val describedAsFacet = getFacet(DescribedAsFacet.class);
-        val describedAs = describedAsFacet.translated();
-        return describedAs == null ? "" : describedAs;
+        return describedAsFacet != null
+                ? _Strings.nullToEmpty(describedAsFacet.translated())
+                : "";
     }
 
     /*
@@ -569,7 +568,7 @@ implements ObjectSpecification {
         Q noopFacet;
 
         @Override
-        public boolean test(Q facet) {
+        public boolean test(final Q facet) {
             if(facet==null) {
                 return false;
             }
@@ -703,7 +702,7 @@ implements ObjectSpecification {
     }
 
     @Override
-    public Stream<ObjectAction> streamRuntimeActions(MixedIn mixedIn) {
+    public Stream<ObjectAction> streamRuntimeActions(final MixedIn mixedIn) {
         val actionTypes = getMetaModelContext().getSystemEnvironment().isPrototyping()
                 ? ActionType.USER_AND_PROTOTYPE
                 : ActionType.USER_ONLY;
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
index 616d915..a03b031 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
@@ -27,6 +27,7 @@ import org.apache.isis.core.metamodel.facetapi.FacetHolderAbstract;
 import org.apache.isis.core.metamodel.facetapi.FacetUtil;
 import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacet;
 import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacetAbstract;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetForMemberName;
 import org.apache.isis.core.metamodel.facets.members.disabled.DisabledFacet;
@@ -123,7 +124,7 @@ public class OneToManyAssociationMixedIn extends OneToManyAssociationDefault imp
 
         if(!isExplicitlyNamed) {
             String memberName = determineNameFrom(mixinAction);
-            FacetUtil.addFacet(new NamedFacetForMemberName(memberName, facetHolder));
+            FacetUtil.addFacet(new NamedFacetForMemberName(NounForm.PLURAL, memberName, facetHolder));
         }
 
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java
index d702a37..0145901 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java
@@ -25,6 +25,7 @@ import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FacetHolderAbstract;
 import org.apache.isis.core.metamodel.facetapi.FacetUtil;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetForMemberName;
 import org.apache.isis.core.metamodel.facets.members.disabled.DisabledFacet;
@@ -106,7 +107,7 @@ public class OneToOneAssociationMixedIn extends OneToOneAssociationDefault imple
 
         if(!isExplicitlyNamed) {
             String memberName = determineNameFrom(mixinAction);
-            FacetUtil.addFacet(new NamedFacetForMemberName(memberName, facetHolder));
+            FacetUtil.addFacet(new NamedFacetForMemberName(NounForm.SINGULAR, memberName, facetHolder));
         }
 
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
index b2f1e4c..a1d91ab 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
@@ -40,10 +40,9 @@ import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.FacetedMethod;
 import org.apache.isis.core.metamodel.facets.ImperativeFacet;
 import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacet;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetForMemberName;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
-import org.apache.isis.core.metamodel.facets.object.plural.inferred.PluralFacetInferred;
 import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
 import org.apache.isis.core.metamodel.facets.object.viewmodel.ViewModelFacet;
 import org.apache.isis.core.metamodel.facets.object.wizard.WizardFacet;
@@ -119,7 +118,7 @@ implements FacetHolder {
         facetedMethodsBuilder.introspectClass();
 
         // name
-        addNamedFacetAndPluralFacetIfRequired();
+        addNamedFacetIfRequired(NounForm.SINGULAR);
 
         // go no further if a value
         if(this.containsFacet(ValueFacet.class)) {
@@ -178,22 +177,15 @@ implements FacetHolder {
         postProcess();
     }
 
-    private void addNamedFacetAndPluralFacetIfRequired() {
-        NamedFacet namedFacet = getFacet(NamedFacet.class);
-        if (namedFacet == null) {
-            namedFacet = new NamedFacetForMemberName(
+    private void addNamedFacetIfRequired(final NounForm nounForm) {
+        if (getFacet(NamedFacet.class) == null) {
+
+            addFacet(new NamedFacetForMemberName(
+                    nounForm,
                     StringExtensions.asNaturalName2(getShortIdentifier()),
-                    this);
-            addFacet(namedFacet);
+                    this));
         }
 
-        PluralFacet pluralFacet = getFacet(PluralFacet.class);
-        if (pluralFacet == null) {
-            pluralFacet = new PluralFacetInferred(
-                    StringExtensions.asPluralName(namedFacet.text()),
-                    this);
-            addFacet(pluralFacet);
-        }
     }
 
 
@@ -244,7 +236,7 @@ implements FacetHolder {
     // -- PREDICATES
 
     @Override
-    public boolean isViewModelCloneable(ManagedObject targetAdapter) {
+    public boolean isViewModelCloneable(final ManagedObject targetAdapter) {
         final ViewModelFacet facet = getFacet(ViewModelFacet.class);
         if(facet == null) {
             return false;
@@ -303,7 +295,7 @@ implements FacetHolder {
         return membersByMethod;
     }
 
-    private void cataloguePropertiesAndCollections(BiConsumer<Method, ObjectMember> onMember) {
+    private void cataloguePropertiesAndCollections(final BiConsumer<Method, ObjectMember> onMember) {
         streamDeclaredAssociations(MixedIn.EXCLUDED)
         .forEach(field->
             field.streamFacets(ImperativeFacet.class)
@@ -312,7 +304,7 @@ implements FacetHolder {
                 .forEach(imperativeFacetMethod->onMember.accept(imperativeFacetMethod, field)));
     }
 
-    private void catalogueActions(BiConsumer<Method, ObjectMember> onMember) {
+    private void catalogueActions(final BiConsumer<Method, ObjectMember> onMember) {
         streamDeclaredActions(MixedIn.INCLUDED)
         .forEach(userAction->
             userAction.streamFacets(ImperativeFacet.class)
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/standalonelist/DescribedAsFacetOnStandaloneList.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/standalonelist/DescribedAsFacetOnStandaloneList.java
deleted file mode 100644
index 7978526..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/standalonelist/DescribedAsFacetOnStandaloneList.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- *  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.specloader.specimpl.standalonelist;
-
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
-import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacetAbstract;
-
-public class DescribedAsFacetOnStandaloneList extends DescribedAsFacetAbstract {
-
-    public DescribedAsFacetOnStandaloneList(final String value, final FacetHolder holder) {
-        super(value, holder);
-    }
-
-}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/standalonelist/PluralFacetOnStandaloneList.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/standalonelist/PluralFacetOnStandaloneList.java
deleted file mode 100644
index 40d186b..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/standalonelist/PluralFacetOnStandaloneList.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- *  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.specloader.specimpl.standalonelist;
-
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacetAbstract;
-
-public class PluralFacetOnStandaloneList extends PluralFacetAbstract {
-
-    public PluralFacetOnStandaloneList(final String value, final FacetHolder holder) {
-        super(value, holder);
-    }
-
-}
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/collections/layout/annotation/NamedFacetForCollectionLayoutAnnotationFactoryTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/collections/layout/annotation/NamedFacetForCollectionLayoutAnnotationFactoryTest.java
index 5a4d6b6..8fa93e8 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/collections/layout/annotation/NamedFacetForCollectionLayoutAnnotationFactoryTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/collections/layout/annotation/NamedFacetForCollectionLayoutAnnotationFactoryTest.java
@@ -33,6 +33,7 @@ import org.apache.isis.applib.annotation.CollectionLayout;
 import org.apache.isis.commons.internal.collections._Sets;
 import org.apache.isis.core.metamodel.facets.AbstractFacetFactoryTest;
 import org.apache.isis.core.metamodel.facets.FacetFactory;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.collections.layout.CollectionLayoutFacetFactory;
 import org.apache.isis.core.metamodel.facets.collections.layout.NamedFacetForCollectionLayoutAnnotation;
@@ -57,7 +58,7 @@ public class NamedFacetForCollectionLayoutAnnotationFactoryTest extends Abstract
         final NamedFacet facet = facetedMethod.getFacet(NamedFacet.class);
         assertThat(facet, is(notNullValue()));
         assertThat(facet, is(instanceOf(NamedFacetForCollectionLayoutAnnotation.class)));
-        assertThat(facet.text(), is(equalTo("1st names")));
+        assertThat(facet.text(NounForm.PLURAL), is(equalTo("1st names")));
         assertThat(facet.escaped(), is(true));
     }
 
@@ -77,7 +78,7 @@ public class NamedFacetForCollectionLayoutAnnotationFactoryTest extends Abstract
         final NamedFacet facet = facetedMethod.getFacet(NamedFacet.class);
         assertThat(facet, is(notNullValue()));
         assertThat(facet, is(instanceOf(NamedFacetForCollectionLayoutAnnotation.class)));
-        assertThat(facet.text(), is(equalTo("1st names")));
+        assertThat(facet.text(NounForm.PLURAL), is(equalTo("1st names")));
         assertThat(facet.escaped(), is(false));
     }
 
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/DomainObjectLayoutFactoryTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/DomainObjectLayoutFactoryTest.java
index 5affb33..0f2181c 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/DomainObjectLayoutFactoryTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/domainobjectlayout/DomainObjectLayoutFactoryTest.java
@@ -32,6 +32,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import org.apache.isis.applib.annotation.BookmarkPolicy;
 import org.apache.isis.applib.annotation.DomainObjectLayout;
@@ -40,12 +41,12 @@ import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facets.AbstractFacetFactoryJUnit4TestCase;
 import org.apache.isis.core.metamodel.facets.FacetFactory;
 import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.members.cssclass.CssClassFacet;
 import org.apache.isis.core.metamodel.facets.members.cssclassfa.CssClassFaFacet;
 import org.apache.isis.core.metamodel.facets.object.bookmarkpolicy.BookmarkPolicyFacet;
 import org.apache.isis.core.metamodel.facets.object.paged.PagedFacet;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 
 public class DomainObjectLayoutFactoryTest
@@ -291,12 +292,11 @@ extends AbstractFacetFactoryJUnit4TestCase {
 
                 facetFactory.process(new FacetFactory.ProcessClassContext(cls, mockMethodRemover, facetHolder));
 
-                final Facet facet = facetHolder.getFacet(NamedFacet.class);
-                assertNotNull(facet);
-                assertTrue(facet instanceof NamedFacetForDomainObjectLayoutAnnotation);
+                final NamedFacet namedFacet = facetHolder.getFacet(NamedFacet.class);
+                assertNotNull(namedFacet);
+                assertTrue(namedFacet instanceof NamedFacetForDomainObjectLayoutAnnotation);
 
-                final NamedFacetForDomainObjectLayoutAnnotation facetImpl = (NamedFacetForDomainObjectLayoutAnnotation) facet;
-                Assert.assertThat(facetImpl.text(), is("Name override"));
+                assertEquals("Name override", namedFacet.text(NounForm.SINGULAR));
 
                 expectNoMethodsRemoved();
             }
@@ -380,12 +380,10 @@ extends AbstractFacetFactoryJUnit4TestCase {
 
                 facetFactory.process(new FacetFactory.ProcessClassContext(cls, mockMethodRemover, facetHolder));
 
-                final Facet facet = facetHolder.getFacet(PluralFacet.class);
-                assertNotNull(facet);
-                assertTrue(facet instanceof PluralFacetForDomainObjectLayoutAnnotation);
+                final NamedFacet namedFacet = facetHolder.getFacet(NamedFacet.class);
+                assertNotNull(namedFacet);
 
-                final PluralFacetForDomainObjectLayoutAnnotation facetImpl = (PluralFacetForDomainObjectLayoutAnnotation) facet;
-                Assert.assertThat(facetImpl.text(), is("Customers Plural Form"));
+                assertEquals("Customers Plural Form", namedFacet.translated(NounForm.PLURAL));
 
                 expectNoMethodsRemoved();
             }
@@ -397,9 +395,10 @@ extends AbstractFacetFactoryJUnit4TestCase {
 
                 facetFactory.process(new FacetFactory.ProcessClassContext(cls, mockMethodRemover, facetHolder));
 
-                final PluralFacet facet = facetHolder.getFacet(PluralFacet.class);
-                assertNotNull(facet);
-                Assert.assertThat(facet.text(), is(""));
+                final NamedFacet namedFacet = facetHolder.getFacet(NamedFacet.class);
+                assertNull(namedFacet);
+
+                //assertEquals("", namedFacet.translated(NounForm.PLURAL));
 
                 expectNoMethodsRemoved();
             }
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/param/layout/annotation/NamedFacetForParameterLayoutAnnotationFactoryTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/param/layout/annotation/NamedFacetForParameterLayoutAnnotationFactoryTest.java
index ac6d747..2281d56 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/param/layout/annotation/NamedFacetForParameterLayoutAnnotationFactoryTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/param/layout/annotation/NamedFacetForParameterLayoutAnnotationFactoryTest.java
@@ -30,6 +30,7 @@ import static org.hamcrest.Matchers.notNullValue;
 import org.apache.isis.applib.annotation.ParameterLayout;
 import org.apache.isis.core.metamodel.facets.AbstractFacetFactoryTest;
 import org.apache.isis.core.metamodel.facets.FacetFactory;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.param.layout.NamedFacetForParameterLayoutAnnotation;
 import org.apache.isis.core.metamodel.facets.param.layout.ParameterLayoutFacetFactory;
@@ -53,7 +54,7 @@ public class NamedFacetForParameterLayoutAnnotationFactoryTest extends AbstractF
         final NamedFacet facet = facetedMethodParameter.getFacet(NamedFacet.class);
         assertThat(facet, is(notNullValue()));
         assertThat(facet, is(instanceOf(NamedFacetForParameterLayoutAnnotation.class)));
-        assertThat(facet.text(), is(equalTo(NAME)));
+        assertEquals(NAME, facet.text(NounForm.SINGULAR));
         assertThat(facet.escaped(), is(true));
     }
 
@@ -72,7 +73,8 @@ public class NamedFacetForParameterLayoutAnnotationFactoryTest extends AbstractF
         final NamedFacet facet = facetedMethodParameter.getFacet(NamedFacet.class);
         assertThat(facet, is(notNullValue()));
         assertThat(facet, is(instanceOf(NamedFacetForParameterLayoutAnnotation.class)));
-        assertThat(facet.text(), is(equalTo(NAME)));
+        assertThat(facet.text(NounForm.SINGULAR), is(equalTo(NAME)));
+        assertEquals(NAME, facet.text(NounForm.SINGULAR));
         assertThat(facet.escaped(), is(false));
     }
 
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/param/name/ParameterNameFacetTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/param/name/ParameterNameFacetTest.java
index ab3dfdd..3888920 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/param/name/ParameterNameFacetTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/param/name/ParameterNameFacetTest.java
@@ -32,6 +32,7 @@ import org.apache.isis.commons.internal.reflection._Reflect;
 import org.apache.isis.core.metamodel._testing.MetaModelContext_forTesting;
 import org.apache.isis.core.metamodel.facets.AbstractFacetFactoryJUnit4TestCase;
 import org.apache.isis.core.metamodel.facets.FacetFactory;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
 import org.apache.isis.core.metamodel.progmodels.dflt.ProgrammingModelFacetsJava8;
@@ -110,7 +111,7 @@ extends AbstractFacetFactoryJUnit4TestCase {
         // then
         val namedFacet = facetedMethodParameter.getFacet(NamedFacet.class);
 
-        assertEquals("An Awesome Name", namedFacet.text());
+        assertEquals("An Awesome Name", namedFacet.text(NounForm.SINGULAR));
 
     }
 
@@ -140,7 +141,7 @@ extends AbstractFacetFactoryJUnit4TestCase {
         // then
         val namedFacet = facetedMethodParameter.getFacet(NamedFacet.class);
         assertNotNull(namedFacet);
-        assertEquals("Even Better Name", namedFacet.text());
+        assertEquals("Even Better Name", namedFacet.text(NounForm.SINGULAR));
 
     }
 
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/NamedFacetForPropertyLayoutAnnotationFactoryTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/NamedFacetForPropertyLayoutAnnotationFactoryTest.java
index f563bb7..27130bb 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/NamedFacetForPropertyLayoutAnnotationFactoryTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/NamedFacetForPropertyLayoutAnnotationFactoryTest.java
@@ -30,6 +30,7 @@ import static org.hamcrest.Matchers.notNullValue;
 import org.apache.isis.applib.annotation.PropertyLayout;
 import org.apache.isis.core.metamodel.facets.AbstractFacetFactoryTest;
 import org.apache.isis.core.metamodel.facets.FacetFactory;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 
 import lombok.val;
@@ -59,7 +60,7 @@ extends AbstractFacetFactoryTest {
         final NamedFacet facet = facetedMethod.getFacet(NamedFacet.class);
         assertThat(facet, is(notNullValue()));
         assertThat(facet, is(instanceOf(NamedFacetForPropertyLayoutAnnotation.class)));
-        assertThat(facet.text(), is(equalTo("1st name")));
+        assertThat(facet.text(NounForm.SINGULAR), is(equalTo("1st name")));
         assertThat(facet.escaped(), is(true));
     }
 
@@ -85,7 +86,7 @@ extends AbstractFacetFactoryTest {
         final NamedFacet facet = facetedMethod.getFacet(NamedFacet.class);
         assertThat(facet, is(notNullValue()));
         assertThat(facet, is(instanceOf(NamedFacetForPropertyLayoutAnnotation.class)));
-        assertThat(facet.text(), is(equalTo("1st name")));
+        assertThat(facet.text(NounForm.SINGULAR), is(equalTo("1st name")));
         assertThat(facet.escaped(), is(false));
     }
 
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/ObjectActionLayoutXmlDefaultTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/ObjectActionLayoutXmlDefaultTest.java
index 3297567..380d5ba 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/ObjectActionLayoutXmlDefaultTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/ObjectActionLayoutXmlDefaultTest.java
@@ -37,12 +37,15 @@ import org.apache.isis.core.metamodel._testing.MetaModelContext_forTesting;
 import org.apache.isis.core.metamodel._testing.TranslationService_forTesting;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facets.FacetedMethod;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetAbstract;
 import org.apache.isis.core.metamodel.id.TypeIdentifierTestFactory;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.core.metamodel.specloader.specimpl.ObjectActionDefault;
 
+import lombok.val;
+
 public class ObjectActionLayoutXmlDefaultTest {
 
     @Rule public JUnitRuleMockery2 context = JUnitRuleMockery2.createFor(Mode.INTERFACES_AND_CLASSES);
@@ -81,8 +84,13 @@ public class ObjectActionLayoutXmlDefaultTest {
     @Test
     public void testNameDefaultsToActionsMethodName() {
         final String name = "Reduceheadcount";
-        final NamedFacet facet = new NamedFacetAbstract(name, true, mockFacetedMethod) {
-        };
+
+        val nounForms = NounForms.preferredSingular()
+        .singular(name)
+        .build();
+
+        final NamedFacet facet =
+                new NamedFacetAbstract(nounForms, true, mockFacetedMethod) {};
         context.checking(new Expectations() {
             {
                 oneOf(mockFacetedMethod).getFacet(NamedFacet.class);
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/OneToManyAssociationDefaultTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/OneToManyAssociationDefaultTest.java
index 026b4d6..ab8d392 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/OneToManyAssociationDefaultTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/OneToManyAssociationDefaultTest.java
@@ -32,10 +32,12 @@ import static org.hamcrest.Matchers.is;
 import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.services.iactn.InteractionProvider;
 import org.apache.isis.applib.services.message.MessageService;
+import org.apache.isis.commons.collections.ImmutableEnumSet;
 import org.apache.isis.core.internaltestsupport.jmocking.JUnitRuleMockery2;
 import org.apache.isis.core.internaltestsupport.jmocking.JUnitRuleMockery2.Mode;
 import org.apache.isis.core.metamodel._testing.MetaModelContext_forTesting;
 import org.apache.isis.core.metamodel.facets.FacetedMethod;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.id.TypeIdentifierTestFactory;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
@@ -131,8 +133,12 @@ public class OneToManyAssociationDefaultTest {
                 oneOf(mockPeer).getFacet(NamedFacet.class);
                 will(returnValue(mockNamedFacet));
 
-                oneOf(mockNamedFacet).translated();
+                allowing(mockNamedFacet).preferredTranslated();
                 will(returnValue("My name"));
+
+                allowing(mockNamedFacet).getSupportedNounForms();
+                will(returnValue(ImmutableEnumSet.of(NounForm.SINGULAR)));
+
             }
         });
     }
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/SpecificationLoaderTestAbstract.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/SpecificationLoaderTestAbstract.java
index 9a84df5..074b17d 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/SpecificationLoaderTestAbstract.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/SpecificationLoaderTestAbstract.java
@@ -44,7 +44,6 @@ import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacet;
 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.facets.collections.CollectionFacet;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
 import org.apache.isis.core.metamodel.progmodels.dflt.ProgrammingModelFacetsJava8;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 
@@ -116,7 +115,6 @@ abstract class SpecificationLoaderTestAbstract {
                 .programmingModelFactory(ProgrammingModelFacetsJava8::new)
                 .translationService(producers.mockTranslationService())
                 .titleService(producers.mockTitleService())
-//                .objectAdapterProvider(mockPersistenceSessionServiceInternal = producers.mockPersistenceSessionServiceInternal())
                 .interactionProvider(mockInteractionProvider =
                     producers.mockInteractionProvider())
                 .singleton(mockMessageService = producers.mockMessageService())
@@ -160,12 +158,6 @@ abstract class SpecificationLoaderTestAbstract {
     }
 
     @Test
-    public void testPluralFaced() throws Exception {
-        final Facet facet = specification.getFacet(PluralFacet.class);
-        assertNotNull(facet);
-    }
-
-    @Test
     public void testDescriptionFacet() throws Exception {
         final Facet facet = specification.getFacet(DescribedAsFacet.class);
         assertNotNull(facet);
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionParameterAbstractTest_getId_and_getName.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionParameterAbstractTest_getId_and_getName.java
index ce0cee9..4b65520 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionParameterAbstractTest_getId_and_getName.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionParameterAbstractTest_getId_and_getName.java
@@ -38,6 +38,7 @@ import org.apache.isis.core.metamodel.consent.Consent;
 import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
 import org.apache.isis.core.metamodel.facets.TypedHolder;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.interactions.InteractionHead;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
@@ -91,8 +92,8 @@ public class ObjectActionParameterAbstractTest_getId_and_getName {
         }
 
         @Override
-        public Consent isValid(InteractionHead head, Can<ManagedObject> pendingArgs,
-                InteractionInitiatedBy interactionInitiatedBy) {
+        public Consent isValid(final InteractionHead head, final Can<ManagedObject> pendingArgs,
+                final InteractionInitiatedBy interactionInitiatedBy) {
             return null;
         }
 
@@ -136,7 +137,7 @@ public class ObjectActionParameterAbstractTest_getId_and_getName {
                 oneOf(actionParamPeer).getFacet(NamedFacet.class);
                 will(returnValue(namedFacet));
 
-                atLeast(1).of(namedFacet).translated();
+                atLeast(1).of(namedFacet).translated(NounForm.SINGULAR);
                 will(returnValue("Some parameter name"));
             }
         });
@@ -154,7 +155,7 @@ public class ObjectActionParameterAbstractTest_getId_and_getName {
                 oneOf(actionParamPeer).getFacet(NamedFacet.class);
                 will(returnValue(namedFacet));
 
-                atLeast(1).of(namedFacet).translated();
+                atLeast(1).of(namedFacet).translated(NounForm.SINGULAR);
                 will(returnValue("Some parameter name"));
             }
         });
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/menubars/bootstrap3/MenuBarsServiceBS3.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/menubars/bootstrap3/MenuBarsServiceBS3.java
index 3061a38..27f7de2 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/menubars/bootstrap3/MenuBarsServiceBS3.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/menubars/bootstrap3/MenuBarsServiceBS3.java
@@ -18,8 +18,21 @@
  */
 package org.apache.isis.core.runtimeservices.menubars.bootstrap3;
 
-import lombok.extern.log4j.Log4j2;
-import lombok.val;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.Priority;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
 import org.apache.isis.applib.annotation.DomainServiceLayout;
 import org.apache.isis.applib.annotation.NatureOfService;
 import org.apache.isis.applib.annotation.PriorityPrecedence;
@@ -40,6 +53,7 @@ import org.apache.isis.commons.internal.collections._Sets;
 import org.apache.isis.core.config.environment.IsisSystemEnvironment;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facets.actions.notinservicemenu.NotInServiceMenuFacet;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.facets.members.layout.group.LayoutGroupFacet;
 import org.apache.isis.core.metamodel.facets.object.domainservice.DomainServiceFacet;
@@ -50,19 +64,9 @@ import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.MixedIn;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.stereotype.Service;
 
-import javax.annotation.Priority;
-import javax.inject.Inject;
-import javax.inject.Named;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
+import lombok.val;
+import lombok.extern.log4j.Log4j2;
 
 @Service
 @Named("isis.runtimeservices.MenuBarsServiceBS3")
@@ -107,7 +111,7 @@ public class MenuBarsServiceBS3 implements MenuBarsService {
 
     // -- HELPER
 
-    private BS3MenuBars loadOrElse(BS3MenuBars fallbackMenuBars) {
+    private BS3MenuBars loadOrElse(final BS3MenuBars fallbackMenuBars) {
 
         val menuBars = Optional.ofNullable(menuBarsLoaderService.menuBars())
                 .map(this::addTnsAndSchemaLocation)
@@ -149,20 +153,20 @@ public class MenuBarsServiceBS3 implements MenuBarsService {
         return menuBars;
     }
 
-    private BS3MenuBars addTnsAndSchemaLocation(BS3MenuBars menuBars) {
+    private BS3MenuBars addTnsAndSchemaLocation(final BS3MenuBars menuBars) {
         menuBars.setTnsAndSchemaLocation(tnsAndSchemaLocation());
         return menuBars;
     }
 
-    private static BS3MenuSection addSectionToMenu(BS3Menu menu) {
+    private static BS3MenuSection addSectionToMenu(final BS3Menu menu) {
         val section = new BS3MenuSection();
         menu.getSections().add(section);
         return section;
     }
 
     private static void bindActionToSection(
-            ServiceActionLayoutData serviceAction,
-            BS3MenuSection section) {
+            final ServiceActionLayoutData serviceAction,
+            final BS3MenuSection section) {
 
         // detach from fallback, attach to this section
         serviceAction.setOwner(section);
@@ -218,7 +222,7 @@ public class MenuBarsServiceBS3 implements MenuBarsService {
         return menuBars;
     }
 
-    private boolean isVisibleAdapterForMenu(ManagedObject objectAdapter) {
+    private boolean isVisibleAdapterForMenu(final ManagedObject objectAdapter) {
         val spec = objectAdapter.getSpecification();
         if (spec.isHidden()) {
             // however, this isn't the same as HiddenObjectFacet, so doesn't filter out
@@ -304,7 +308,7 @@ public class MenuBarsServiceBS3 implements MenuBarsService {
         // first, order as defined in isis.properties
         for (ManagedObject serviceAdapter : serviceAdapters) {
             final ObjectSpecification serviceSpec = serviceAdapter.getSpecification();
-            String serviceName = serviceSpec.getFacet(NamedFacet.class).translated();
+            String serviceName = serviceSpec.getFacet(NamedFacet.class).translated(NounForm.SINGULAR);
             serviceNameOrder.add(serviceName);
         }
         // then, any other services (eg due to misspellings, at the end)
@@ -370,9 +374,11 @@ public class MenuBarsServiceBS3 implements MenuBarsService {
                 .filter(objectAction->objectAction.getFacet(NotInServiceMenuFacet.class) == null)
                 .map(objectAction->{
                     val layoutGroupFacet = objectAction.getFacet(LayoutGroupFacet.class);
-                    String serviceName = layoutGroupFacet != null ? layoutGroupFacet.getGroupId(): null;
+                    String serviceName = layoutGroupFacet != null
+                            ? layoutGroupFacet.getGroupId()
+                            : null;
                     if(_Strings.isNullOrEmpty(serviceName)){
-                        serviceName = serviceSpec.getFacet(NamedFacet.class).translated();
+                        serviceName = serviceSpec.getFacet(NamedFacet.class).translated(NounForm.SINGULAR);
                     }
                     return new ServiceAndAction(serviceName, serviceAdapter, objectAction);
                 });
@@ -380,7 +386,7 @@ public class MenuBarsServiceBS3 implements MenuBarsService {
     }
 
     private static Predicate<ManagedObject> with(final DomainServiceLayout.MenuBar menuBar) {
-        return (ManagedObject input) -> {
+        return (final ManagedObject input) -> {
             final DomainServiceLayoutFacet facet =
                     input.getSpecification().getFacet(DomainServiceLayoutFacet.class);
             return facet != null && facet.getMenuBar() == menuBar;
diff --git a/persistence/jdo/metamodel/src/test/java/org/apache/isis/persistence/jdo/metamodel/specloader/ObjectReflectorDefaultTest_object.java b/persistence/jdo/metamodel/src/test/java/org/apache/isis/persistence/jdo/metamodel/specloader/ObjectReflectorDefaultTest_object.java
index 2c89745..2c221fd 100644
--- a/persistence/jdo/metamodel/src/test/java/org/apache/isis/persistence/jdo/metamodel/specloader/ObjectReflectorDefaultTest_object.java
+++ b/persistence/jdo/metamodel/src/test/java/org/apache/isis/persistence/jdo/metamodel/specloader/ObjectReflectorDefaultTest_object.java
@@ -32,7 +32,6 @@ 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.facets.collections.CollectionFacet;
 import org.apache.isis.core.metamodel.facets.object.objectvalidprops.ObjectValidPropertiesFacet;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
 import org.apache.isis.core.metamodel.facets.object.title.TitleFacet;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
@@ -62,7 +61,6 @@ class ObjectReflectorDefaultTest_object extends SpecificationLoaderTestAbstract
         assertNotNull(specification.getFacet(NamedFacet.class));
         assertNotNull(specification.getFacet(DescribedAsFacet.class));
         assertNotNull(specification.getFacet(TitleFacet.class));
-        assertNotNull(specification.getFacet(PluralFacet.class));
         assertNotNull(specification.getFacet(ObjectValidPropertiesFacet.class));
     }
 
diff --git a/persistence/jdo/metamodel/src/test/java/org/apache/isis/persistence/jdo/metamodel/specloader/SpecificationLoaderTestAbstract.java b/persistence/jdo/metamodel/src/test/java/org/apache/isis/persistence/jdo/metamodel/specloader/SpecificationLoaderTestAbstract.java
index a507d5f..8b9a718 100644
--- a/persistence/jdo/metamodel/src/test/java/org/apache/isis/persistence/jdo/metamodel/specloader/SpecificationLoaderTestAbstract.java
+++ b/persistence/jdo/metamodel/src/test/java/org/apache/isis/persistence/jdo/metamodel/specloader/SpecificationLoaderTestAbstract.java
@@ -44,7 +44,6 @@ import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacet;
 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.facets.collections.CollectionFacet;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
 import org.apache.isis.core.metamodel.progmodels.dflt.ProgrammingModelFacetsJava8;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
@@ -161,12 +160,6 @@ abstract class SpecificationLoaderTestAbstract {
     }
 
     @Test
-    public void testPluralFaced() throws Exception {
-        final Facet facet = specification.getFacet(PluralFacet.class);
-        assertNotNull(facet);
-    }
-
-    @Test
     public void testDescriptionFacet() throws Exception {
         final Facet facet = specification.getFacet(DescribedAsFacet.class);
         assertNotNull(facet);
diff --git a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/action/ActionUiMetaModel.java b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/action/ActionUiMetaModel.java
index 652ed42..9a81156 100644
--- a/viewers/common/src/main/java/org/apache/isis/viewer/common/model/action/ActionUiMetaModel.java
+++ b/viewers/common/src/main/java/org/apache/isis/viewer/common/model/action/ActionUiMetaModel.java
@@ -85,7 +85,7 @@ public final class ActionUiMetaModel implements Serializable {
             final ObjectAction objectAction) {
 
         this(   objectAction.getMemento(),
-                ObjectAction.Util.nameFor(objectAction),
+                objectAction.getName(),
                 getDescription(objectAction).orElse(ObjectAction.Util.descriptionOf(objectAction)),
                 ObjectAction.Util.returnsBlobOrClob(objectAction),
                 objectAction.isPrototype(),
diff --git a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModelStandalone.java b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModelStandalone.java
index e53b92d..efa9940 100644
--- a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModelStandalone.java
+++ b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModelStandalone.java
@@ -22,7 +22,8 @@ import java.util.List;
 
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.base._NullSafe;
-import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
+import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
+import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
 import org.apache.isis.core.runtime.memento.ObjectMemento;
@@ -95,8 +96,8 @@ extends EntityCollectionModelAbstract {
 
     @Override
     public String getName() {
-        return getTypeOfSpecification().lookupFacet(PluralFacet.class)
-                .map(PluralFacet::translated)
+        return getTypeOfSpecification().lookupFacet(NamedFacet.class)
+                .map(namedFacet->namedFacet.translated(NounForm.PLURAL))
                 .orElse(getIdentifier().getMemberLogicalName());
     }