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/24 20:29:33 UTC

[isis] branch master updated: ISIS-1720: static and dynamic naming are profoundly different beasts ...

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 4bfabb2  ISIS-1720: static and dynamic naming are profoundly different beasts ...
4bfabb2 is described below

commit 4bfabb2721aad66b003fe0d4bd812c8c0fee6ecc
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Jun 24 22:27:20 2021 +0200

    ISIS-1720: static and dynamic naming are profoundly different beasts ...
    
    hence reworking NamedFacet and DescribedAsFacet to support 2 branches of
    specialization, expressed through interfaces
    - HasTranslation ... static naming
    - HasImperativeText ... dynamic (imperative) naming
    
    yet leaving some TODO markers
---
 ...nChoicesForCollectionParameterFacetFactory.java |   6 +-
 .../facets/all/described/DescribedAsFacet.java     |  13 +-
 .../all/described/DescribedAsFacetAbstract.java    |  17 +++
 .../metamodel/facets/all/i8n/HasTranslation.java   |   9 ++
 .../facets/all/i8n/I8nImperativeFacetAbstract.java |  62 ----------
 .../imperative/HasImperativeText.java}             |  35 +++---
 .../i8n/imperative/I8nImperativeFacetAbstract.java |  95 +++++++++++++++
 .../metamodel/facets/all/named/NamedFacet.java     |   7 +-
 .../facets/all/named/NamedFacetAbstract.java       |   7 ++
 .../DescribedAsFacetOnMemberDerivedFromType.java   |  20 +++-
 .../annotprop/DescribedAsFacetOnMemberFactory.java |   5 +-
 .../method/DescribedAsFacetForMemberViaMethod.java |  10 +-
 .../DescribedAsFacetForMemberViaMethodFactory.java |   4 +-
 .../named/method/NamedFacetForMemberViaMethod.java |  10 +-
 .../NamedFacetForMemberViaMethodFactory.java       |   4 +-
 .../title/methods/TitleFacetViaMethodsFactory.java |   3 +-
 .../title/methods/TitleFacetViaTitleMethod.java    |  13 +-
 .../mandatory/MandatoryFacetAbstract.java          |  11 +-
 ...ameterAnnotationElseDerivedFromTypeFactory.java |   4 +-
 ...DescribedAsFacetOnParameterDerivedFromType.java |  20 +++-
 .../method/PropertyValidateFacetViaMethod.java     |  11 +-
 .../PropertyValidateFacetViaMethodFactory.java     |   3 +-
 .../core/metamodel/layout/LayoutFacetUtil.java     |  17 ++-
 .../DeriveDescribedAsFromTypePostProcessor.java    |  25 ++--
 .../all/i18n/TranslationPostProcessor.java         |  12 +-
 .../isis/core/metamodel/spec/ManagedObjects.java   | 133 +++++++++++++--------
 .../core/metamodel/spec/feature/ObjectFeature.java |   4 +
 .../specimpl/ObjectActionParameterAbstract.java    |  25 ++--
 .../specloader/specimpl/ObjectMemberAbstract.java  |  21 +++-
 .../specimpl/ObjectSpecificationAbstract.java      |  53 +++-----
 ...etForCollectionLayoutAnnotationFactoryTest.java |   5 +-
 .../DomainObjectLayoutFactoryTest.java             |   5 +-
 .../ident/title/TitleFacetViaMethodTest.java       |  14 +--
 ...cetForParameterLayoutAnnotationFactoryTest.java |   7 +-
 .../facets/param/name/ParameterNameFacetTest.java  |   5 +-
 ...acetForPropertyLayoutAnnotationFactoryTest.java |   5 +-
 .../objects/OneToManyAssociationDefaultTest.java   |   9 +-
 ...ionParameterAbstractTest_getId_and_getName.java |  10 +-
 .../menubars/bootstrap3/MenuBarsServiceBS3.java    |   9 +-
 .../testdomain/interact/NewParameterModelTest.java |   3 +-
 .../common/model/action/ActionUiMetaModel.java     |  12 +-
 .../wicket/model/models/EntityCollectionModel.java |   5 +-
 .../model/models/EntityCollectionModelDummy.java   |   5 +
 .../models/EntityCollectionModelParented.java      |   7 +-
 .../models/EntityCollectionModelStandalone.java    |  14 ++-
 .../CollectionContentsAsAjaxTablePanel.java        |  17 ++-
 46 files changed, 511 insertions(+), 280 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionChoicesForCollectionParameterFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionChoicesForCollectionParameterFacetFactory.java
index de3d86a..c6f24c4 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionChoicesForCollectionParameterFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionChoicesForCollectionParameterFacetFactory.java
@@ -59,7 +59,7 @@ implements MetaModelRefiner {
     }
 
     @Override
-    public void refineProgrammingModel(ProgrammingModel programmingModel) {
+    public void refineProgrammingModel(final ProgrammingModel programmingModel) {
 
         val shouldCheck = getConfiguration().getCore().getMetaModel().getValidator().isActionCollectionParameterChoices();
         if(!shouldCheck) {
@@ -112,7 +112,7 @@ implements MetaModelRefiner {
                         String.format(
                                 messageFormat,
                                 objectSpec.getFullIdentifier(),
-                                objectAction.getName(),
+                                objectAction.getId(),
                                 paramNum));
 
                 return;
@@ -136,7 +136,7 @@ implements MetaModelRefiner {
                 String.format(
                         messageFormat,
                         objectSpec.getFullIdentifier(),
-                        objectAction.getName(),
+                        objectAction.getId(),
                         paramNum));
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/described/DescribedAsFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/described/DescribedAsFacet.java
index 70381b4..c06eeb8 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/described/DescribedAsFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/described/DescribedAsFacet.java
@@ -19,8 +19,10 @@
 
 package org.apache.isis.core.metamodel.facets.all.described;
 
+import org.apache.isis.commons.internal.base._Either;
 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.all.i8n.imperative.HasImperativeText;
 
 /**
  * Describes a class, a property, collection, an action or an action parameter.
@@ -31,15 +33,8 @@ import org.apache.isis.core.metamodel.facets.all.i8n.HasTranslation;
  */
 public interface DescribedAsFacet
 extends
-    Facet,
-    HasTranslation {
+    Facet {
 
-    default String text() {
-        return preferredText();
-    }
-
-    default String translated() {
-        return preferredTranslated();
-    }
+    _Either<HasTranslation, HasImperativeText> getSpecialization();
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/described/DescribedAsFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/described/DescribedAsFacetAbstract.java
index ddc23b9..ecea7d1 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/described/DescribedAsFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/described/DescribedAsFacetAbstract.java
@@ -19,10 +19,15 @@
 
 package org.apache.isis.core.metamodel.facets.all.described;
 
+import org.apache.isis.commons.internal.base._Either;
 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.HasTranslation;
 import org.apache.isis.core.metamodel.facets.all.i8n.I8nFacetAbstract;
 import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
+import org.apache.isis.core.metamodel.facets.all.i8n.imperative.HasImperativeText;
+
+import lombok.Getter;
 
 public abstract class DescribedAsFacetAbstract
 extends I8nFacetAbstract
@@ -32,6 +37,9 @@ implements DescribedAsFacet {
         return DescribedAsFacet.class;
     }
 
+    @Getter(onMethod_ = {@Override})
+    private final _Either<HasTranslation, HasImperativeText> specialization = _Either.left(this);
+
     protected DescribedAsFacetAbstract(
             final String originalText,
             final FacetHolder holder) {
@@ -50,5 +58,14 @@ implements DescribedAsFacet {
                 precedence);
     }
 
+    public final String text() {
+        return preferredText();
+    }
+
+    public final String translated() {
+        return preferredTranslated();
+    }
+
+
 }
 
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 d04da8f..77bdf1c 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,5 +1,7 @@
 package org.apache.isis.core.metamodel.facets.all.i8n;
 
+import javax.annotation.Nullable;
+
 import org.apache.isis.commons.collections.ImmutableEnumSet;
 
 public interface HasTranslation {
@@ -26,6 +28,13 @@ public interface HasTranslation {
 
     ImmutableEnumSet<NounForm> getSupportedNounForms();
 
+    @Nullable
+    default String translatedElseNull(final NounForm nounForm) {
+        return getSupportedNounForms().contains(nounForm)
+                ? translated(nounForm)
+                : null;
+    }
+
     default void memoizeTranslations() {
         getSupportedNounForms().forEach(this::translated);
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/I8nImperativeFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/I8nImperativeFacetAbstract.java
deleted file mode 100644
index 204d51d..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/I8nImperativeFacetAbstract.java
+++ /dev/null
@@ -1,62 +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.all.i8n;
-
-import java.lang.reflect.Method;
-
-import org.apache.isis.commons.collections.Can;
-import org.apache.isis.core.metamodel.facetapi.Facet;
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
-import org.apache.isis.core.metamodel.facets.ImperativeFacet;
-
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.NonNull;
-
-public class I8nImperativeFacetAbstract
-extends I8nFacetAbstract
-implements ImperativeFacet {
-
-    @Getter(value = AccessLevel.PROTECTED)
-    private final @NonNull Method method;
-
-    protected I8nImperativeFacetAbstract(
-            final Class<? extends Facet> facetType,
-            final Method method,
-            final FacetHolder holder) {
-        super(facetType,
-                NounForms
-                .preferredIndifferent("TODO")
-                .build(),
-                holder,
-                Precedence.HIGH);
-        this.method = method;
-    }
-
-    @Override
-    public Can<Method> getMethods() {
-        return Can.ofSingleton(method);
-    }
-
-    @Override
-    public Intent getIntent(final Method method) {
-        return Intent.UI_HINT;
-    }
-
-}
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/i8n/imperative/HasImperativeText.java
similarity index 53%
copy from core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/named/NamedFacet.java
copy to core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/imperative/HasImperativeText.java
index 3cced9d..3016c20 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/i8n/imperative/HasImperativeText.java
@@ -16,26 +16,29 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
+package org.apache.isis.core.metamodel.facets.all.i8n.imperative;
 
-package org.apache.isis.core.metamodel.facets.all.named;
+import javax.annotation.Nullable;
 
-import org.apache.isis.core.metamodel.facetapi.Facet;
-import org.apache.isis.core.metamodel.facets.all.i8n.HasTranslation;
+import org.apache.isis.applib.services.i18n.TranslatableString;
+import org.apache.isis.commons.functional.Result;
+import org.apache.isis.core.metamodel.spec.ManagedObject;
 
-/**
- * The name of a class, a property, collection, an action or a parameter.
- *
- * <p>
- * In the standard Apache Isis Programming Model, corresponds to annotating the
- * member with <tt>@Named</tt>.
- */
-public interface NamedFacet
-extends
-    Facet,
-    HasTranslation {
+public interface HasImperativeText {
 
     /**
-     * Flag indicating whether the label should be show as is, or should be HTML escaped.
+     * Provide text for the target object.
+     * <p>
+     * Translated or not, based on whether corresponding support method returns
+     * {@link TranslatableString} or just {@link String}.
+     * <p>
+     * eg. title, name, description
      */
-    boolean escaped();
+    Result<String> text(ManagedObject object);
+
+    @Nullable
+    default String textElseNull(final ManagedObject object) {
+        return text(object).optionalElseFail().orElse(null);
+    }
+
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/imperative/I8nImperativeFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/imperative/I8nImperativeFacetAbstract.java
new file mode 100644
index 0000000..8e05beb
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i8n/imperative/I8nImperativeFacetAbstract.java
@@ -0,0 +1,95 @@
+/*
+ *  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.imperative;
+
+import java.lang.reflect.Method;
+import java.util.Objects;
+import java.util.function.BiConsumer;
+
+import org.apache.isis.applib.services.i18n.TranslationContext;
+import org.apache.isis.commons.collections.Can;
+import org.apache.isis.commons.functional.Result;
+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.ImperativeFacet;
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.ManagedObjects;
+
+import lombok.NonNull;
+import lombok.val;
+
+public class I8nImperativeFacetAbstract
+extends FacetAbstract
+implements
+    ImperativeFacet,
+    HasImperativeText {
+
+    protected final TranslationContext translationContext;
+    protected final @NonNull Method method;
+
+    protected I8nImperativeFacetAbstract(
+            final Class<? extends Facet> facetType,
+            final Method method,
+            final FacetHolder holder) {
+        super(facetType, holder, Precedence.HIGH);
+        this.method = method;
+        this.translationContext = TranslationContext
+                .forTranslationContextHolder(holder.getFeatureIdentifier());
+    }
+
+    @Override
+    public final Result<String> text(final ManagedObject object) {
+        return ManagedObjects.imperativeText(object, method, translationContext);
+    }
+
+    @Override
+    public final Can<Method> getMethods() {
+        return Can.ofSingleton(method);
+    }
+
+    @Override
+    public final Intent getIntent(final Method method) {
+        return Intent.UI_HINT;
+    }
+
+    @Override
+    public final void visitAttributes(final BiConsumer<String, Object> visitor) {
+        super.visitAttributes(visitor);
+        visitor.accept("context", translationContext);
+        ImperativeFacet.visitAttributes(this, visitor);
+    }
+
+    @Override
+    public final boolean semanticEquals(final @NonNull Facet other) {
+
+        // equality by facet-type, java-method and translation-context
+
+        if(!this.facetType().equals(other.facetType())) {
+            return false;
+        }
+
+        val otherFacet = (I8nImperativeFacetAbstract)other;
+
+        return Objects.equals(this.method, otherFacet.method)
+                && Objects.equals(this.translationContext, otherFacet.translationContext);
+
+    }
+
+}
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 3cced9d..3831d79 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
@@ -19,8 +19,10 @@
 
 package org.apache.isis.core.metamodel.facets.all.named;
 
+import org.apache.isis.commons.internal.base._Either;
 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.all.i8n.imperative.HasImperativeText;
 
 /**
  * The name of a class, a property, collection, an action or a parameter.
@@ -31,8 +33,9 @@ import org.apache.isis.core.metamodel.facets.all.i8n.HasTranslation;
  */
 public interface NamedFacet
 extends
-    Facet,
-    HasTranslation {
+    Facet {
+
+    _Either<HasTranslation, HasImperativeText> getSpecialization();
 
     /**
      * 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 7dbd9af..35346ea 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
@@ -21,11 +21,15 @@ package org.apache.isis.core.metamodel.facets.all.named;
 
 import java.util.function.BiConsumer;
 
+import org.apache.isis.commons.internal.base._Either;
 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.HasTranslation;
 import org.apache.isis.core.metamodel.facets.all.i8n.I8nFacetAbstract;
 import org.apache.isis.core.metamodel.facets.all.i8n.NounForms;
+import org.apache.isis.core.metamodel.facets.all.i8n.imperative.HasImperativeText;
 
+import lombok.Getter;
 import lombok.NonNull;
 import lombok.val;
 
@@ -37,6 +41,9 @@ implements NamedFacet {
         return NamedFacet.class;
     }
 
+    @Getter(onMethod_ = {@Override})
+    private final _Either<HasTranslation, HasImperativeText> specialization = _Either.left(this);
+
     private final boolean escaped;
 
     protected NamedFacetAbstract(
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/annotprop/DescribedAsFacetOnMemberDerivedFromType.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/annotprop/DescribedAsFacetOnMemberDerivedFromType.java
index 1816abb..3bf1060 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/annotprop/DescribedAsFacetOnMemberDerivedFromType.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/annotprop/DescribedAsFacetOnMemberDerivedFromType.java
@@ -19,6 +19,8 @@
 
 package org.apache.isis.core.metamodel.facets.members.described.annotprop;
 
+import java.util.Optional;
+
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.all.described.DescribedAsFacet;
 import org.apache.isis.core.metamodel.facets.all.described.DescribedAsFacetAbstract;
@@ -26,9 +28,25 @@ import org.apache.isis.core.metamodel.facets.all.described.DescribedAsFacetAbstr
 public class DescribedAsFacetOnMemberDerivedFromType
 extends DescribedAsFacetAbstract {
 
-    public DescribedAsFacetOnMemberDerivedFromType(
+    /**
+     * As {@link DescribedAsFacet}(s) have either static or dynamic (imperative) text,
+     * we yet only support inferring from those with static text.
+     */
+    public static Optional<DescribedAsFacet> create(
             final DescribedAsFacet describedAsFacet,
             final FacetHolder holder) {
+
+        return describedAsFacet instanceof DescribedAsFacetAbstract
+                ? Optional.of(
+                        new DescribedAsFacetOnMemberDerivedFromType(
+                                (DescribedAsFacetAbstract) describedAsFacet,
+                                holder))
+                : Optional.empty();
+    }
+
+    private DescribedAsFacetOnMemberDerivedFromType(
+            final DescribedAsFacetAbstract describedAsFacet,
+            final FacetHolder holder) {
         super(describedAsFacet.text(), holder);
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/annotprop/DescribedAsFacetOnMemberFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/annotprop/DescribedAsFacetOnMemberFactory.java
index 453f2d7..568e3c1 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/annotprop/DescribedAsFacetOnMemberFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/annotprop/DescribedAsFacetOnMemberFactory.java
@@ -46,8 +46,9 @@ extends FacetFactoryAbstract {
 
         addFacetIfPresent(
                 paramTypeSpec.lookupFacet(DescribedAsFacet.class)
-                .map(returnTypeDescribedAsFacet->
-                    new DescribedAsFacetOnMemberDerivedFromType(
+                .flatMap(returnTypeDescribedAsFacet->
+                    DescribedAsFacetOnMemberDerivedFromType
+                    .create(
                             returnTypeDescribedAsFacet,
                             processMethodContext.getFacetHolder())));
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/method/DescribedAsFacetForMemberViaMethod.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/method/DescribedAsFacetForMemberViaMethod.java
index 0346f31..aa0b9cb 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/method/DescribedAsFacetForMemberViaMethod.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/method/DescribedAsFacetForMemberViaMethod.java
@@ -20,10 +20,15 @@ package org.apache.isis.core.metamodel.facets.members.described.method;
 
 import java.lang.reflect.Method;
 
+import org.apache.isis.commons.internal.base._Either;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.all.described.DescribedAsFacet;
-import org.apache.isis.core.metamodel.facets.all.i8n.I8nImperativeFacetAbstract;
+import org.apache.isis.core.metamodel.facets.all.i8n.HasTranslation;
+import org.apache.isis.core.metamodel.facets.all.i8n.imperative.HasImperativeText;
+import org.apache.isis.core.metamodel.facets.all.i8n.imperative.I8nImperativeFacetAbstract;
+
+import lombok.Getter;
 
 public class DescribedAsFacetForMemberViaMethod
 extends I8nImperativeFacetAbstract
@@ -34,6 +39,9 @@ implements
         return DescribedAsFacet.class;
     }
 
+    @Getter(onMethod_ = {@Override})
+    private final _Either<HasTranslation, HasImperativeText> specialization = _Either.right(this);
+
     public DescribedAsFacetForMemberViaMethod(
             final Method describedMethod,
             final FacetHolder holder) {
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/method/DescribedAsFacetForMemberViaMethodFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/method/DescribedAsFacetForMemberViaMethodFactory.java
index 178e578..2cbdf32 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/method/DescribedAsFacetForMemberViaMethodFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/described/method/DescribedAsFacetForMemberViaMethodFactory.java
@@ -23,7 +23,6 @@ import javax.inject.Inject;
 
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FacetUtil;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
 import org.apache.isis.core.metamodel.methods.MethodFinder;
@@ -66,11 +65,10 @@ extends MethodPrefixBasedFacetFactoryAbstract {
         }
         processMethodContext.removeMethod(describedMethod);
 
-        final FacetHolder facetedMethod = processMethodContext.getFacetHolder();
         FacetUtil.addFacet(
                 new DescribedAsFacetForMemberViaMethod(
                         describedMethod,
-                        facetedMethod));
+                        processMethodContext.getFacetHolder()));
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/named/method/NamedFacetForMemberViaMethod.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/named/method/NamedFacetForMemberViaMethod.java
index 5954503..95f1825 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/named/method/NamedFacetForMemberViaMethod.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/named/method/NamedFacetForMemberViaMethod.java
@@ -20,11 +20,16 @@ package org.apache.isis.core.metamodel.facets.members.named.method;
 
 import java.lang.reflect.Method;
 
+import org.apache.isis.commons.internal.base._Either;
 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.I8nImperativeFacetAbstract;
+import org.apache.isis.core.metamodel.facets.all.i8n.HasTranslation;
+import org.apache.isis.core.metamodel.facets.all.i8n.imperative.HasImperativeText;
+import org.apache.isis.core.metamodel.facets.all.i8n.imperative.I8nImperativeFacetAbstract;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 
+import lombok.Getter;
+
 public class NamedFacetForMemberViaMethod
 extends I8nImperativeFacetAbstract
 implements NamedFacet {
@@ -33,6 +38,9 @@ implements NamedFacet {
         return NamedFacet.class;
     }
 
+    @Getter(onMethod_ = {@Override})
+    private final _Either<HasTranslation, HasImperativeText> specialization = _Either.right(this);
+
     public NamedFacetForMemberViaMethod(
             final Method namedMethod,
             final FacetHolder holder) {
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/named/method/NamedFacetForMemberViaMethodFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/named/method/NamedFacetForMemberViaMethodFactory.java
index 97e3208..6ac1614 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/named/method/NamedFacetForMemberViaMethodFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/members/named/method/NamedFacetForMemberViaMethodFactory.java
@@ -23,7 +23,6 @@ import javax.inject.Inject;
 
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FacetUtil;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
 import org.apache.isis.core.metamodel.methods.MethodFinder;
@@ -66,11 +65,10 @@ extends MethodPrefixBasedFacetFactoryAbstract {
         }
         processMethodContext.removeMethod(namedMethod);
 
-        final FacetHolder facetedMethod = processMethodContext.getFacetHolder();
         FacetUtil.addFacet(
                 new NamedFacetForMemberViaMethod(
                         namedMethod,
-                        facetedMethod));
+                        processMethodContext.getFacetHolder()));
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/methods/TitleFacetViaMethodsFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/methods/TitleFacetViaMethodsFactory.java
index 2e9d6e3..6be2c8c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/methods/TitleFacetViaMethodsFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/methods/TitleFacetViaMethodsFactory.java
@@ -68,13 +68,12 @@ extends MethodPrefixBasedFacetFactoryAbstract {
                 NO_ARG);
         if (method != null) {
             processClassContext.removeMethod(method);
-            val translationService = getTranslationService();
             // sadness: same as in TranslationFactory
             val translationContext = TranslationContext.forMethod(method);
 
             FacetUtil.addFacet(
                     new TitleFacetViaTitleMethod(
-                            method, translationService, translationContext, facetHolder));
+                            method, translationContext, facetHolder));
             return;
         }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/methods/TitleFacetViaTitleMethod.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/methods/TitleFacetViaTitleMethod.java
index 6e9fdbb..95cf4f0 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/methods/TitleFacetViaTitleMethod.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/methods/TitleFacetViaTitleMethod.java
@@ -24,7 +24,6 @@ import java.util.function.BiConsumer;
 
 import org.apache.isis.applib.services.i18n.TranslatableString;
 import org.apache.isis.applib.services.i18n.TranslationContext;
-import org.apache.isis.applib.services.i18n.TranslationService;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.ImperativeFacet;
@@ -43,14 +42,14 @@ extends TitleFacetAbstract
 implements ImperativeFacet {
 
     @Getter(onMethod_ = {@Override}) private final @NonNull Can<Method> methods;
-    private final TranslationService translationService;
     private final TranslationContext translationContext;
 
-    public TitleFacetViaTitleMethod(final Method method, final TranslationService translationService,
-    		final TranslationContext translationContext, final FacetHolder holder) {
+    public TitleFacetViaTitleMethod(
+            final Method method,
+    		final TranslationContext translationContext,
+    		final FacetHolder holder) {
         super(holder);
         this.methods = Can.ofSingleton(method);
-        this.translationService = translationService;
         this.translationContext = translationContext;
     }
 
@@ -74,12 +73,12 @@ implements ImperativeFacet {
             }
             if(returnValue instanceof TranslatableString) {
                 final TranslatableString ts = (TranslatableString) returnValue;
-                return ts.translate(translationService, translationContext);
+                return ts.translate(getTranslationService(), translationContext);
             }
             return null;
         } catch (final RuntimeException ex) {
 
-            val isUnitTesting = super.getMetaModelContext().getSystemEnvironment().isUnitTesting();
+            val isUnitTesting = getMetaModelContext().getSystemEnvironment().isUnitTesting();
 
             if(!isUnitTesting) {
                 log.warn("Title failure", ex);
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 58b38dc..80334c6 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
@@ -102,10 +102,13 @@ implements MandatoryFacet {
         if (!required) {
             return null;
         }
-        final NamedFacet namedFacet = getFacetHolder().getFacet(NamedFacet.class);
-        final String name = namedFacet != null
-                ? namedFacet.preferredTranslated()
-                : null;
+        val name = getFacetHolder().lookupFacet(NamedFacet.class)
+        .map(NamedFacet::getSpecialization)
+        .map(specialization->specialization
+                .fold(textFacet->textFacet.preferredTranslated(),
+                      textFacet->textFacet.textElseNull(context.getHead().getTarget())))
+        .orElse(null);
+
         return name != null
                 ? "'" + name + "' is mandatory"
                 : "Mandatory";
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/described/annotderived/DescribedAsFacetOnParameterAnnotationElseDerivedFromTypeFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/described/annotderived/DescribedAsFacetOnParameterAnnotationElseDerivedFromTypeFactory.java
index 1617bdf..8107f49 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/described/annotderived/DescribedAsFacetOnParameterAnnotationElseDerivedFromTypeFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/described/annotderived/DescribedAsFacetOnParameterAnnotationElseDerivedFromTypeFactory.java
@@ -47,7 +47,9 @@ extends FacetFactoryAbstract {
         // available
         final DescribedAsFacet parameterTypeDescribedAsFacet = getDescribedAsFacet(parameterType);
         if (parameterTypeDescribedAsFacet != null) {
-            FacetUtil.addFacet(new DescribedAsFacetOnParameterDerivedFromType(parameterTypeDescribedAsFacet, processParameterContext.getFacetHolder()));
+            FacetUtil.addFacetIfPresent(
+                    DescribedAsFacetOnParameterDerivedFromType
+                    .create(parameterTypeDescribedAsFacet, processParameterContext.getFacetHolder()));
         }
 
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/described/annotderived/DescribedAsFacetOnParameterDerivedFromType.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/described/annotderived/DescribedAsFacetOnParameterDerivedFromType.java
index faddde5..bb551c6 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/described/annotderived/DescribedAsFacetOnParameterDerivedFromType.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/described/annotderived/DescribedAsFacetOnParameterDerivedFromType.java
@@ -19,6 +19,8 @@
 
 package org.apache.isis.core.metamodel.facets.param.described.annotderived;
 
+import java.util.Optional;
+
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.all.described.DescribedAsFacet;
 import org.apache.isis.core.metamodel.facets.all.described.DescribedAsFacetAbstract;
@@ -26,9 +28,25 @@ import org.apache.isis.core.metamodel.facets.all.described.DescribedAsFacetAbstr
 public class DescribedAsFacetOnParameterDerivedFromType
 extends DescribedAsFacetAbstract {
 
-    public DescribedAsFacetOnParameterDerivedFromType(
+    /**
+     * As {@link DescribedAsFacet}(s) have either static or dynamic (imperative) text,
+     * we yet only support inferring from those with static text.
+     */
+    public static Optional<DescribedAsFacet> create(
             final DescribedAsFacet describedAsFacet,
             final FacetHolder holder) {
+
+        return describedAsFacet instanceof DescribedAsFacetAbstract
+                ? Optional.of(
+                        new DescribedAsFacetOnParameterDerivedFromType(
+                                (DescribedAsFacetAbstract) describedAsFacet,
+                                holder))
+                : Optional.empty();
+    }
+
+    private DescribedAsFacetOnParameterDerivedFromType(
+            final DescribedAsFacetAbstract describedAsFacet,
+            final FacetHolder holder) {
         super(describedAsFacet.text(), holder);
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/validating/method/PropertyValidateFacetViaMethod.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/validating/method/PropertyValidateFacetViaMethod.java
index 6fbd6ae..fbb53b7 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/validating/method/PropertyValidateFacetViaMethod.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/validating/method/PropertyValidateFacetViaMethod.java
@@ -24,7 +24,6 @@ import java.util.function.BiConsumer;
 
 import org.apache.isis.applib.services.i18n.TranslatableString;
 import org.apache.isis.applib.services.i18n.TranslationContext;
-import org.apache.isis.applib.services.i18n.TranslationService;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.ImperativeFacet;
@@ -35,14 +34,14 @@ import org.apache.isis.core.metamodel.spec.ManagedObjects;
 public class PropertyValidateFacetViaMethod extends PropertyValidateFacetAbstract implements ImperativeFacet {
 
     private final Method method;
-    private final TranslationService translationService;
     private final TranslationContext translationContext;
 
-    public PropertyValidateFacetViaMethod(final Method method, final TranslationService translationService,
-    		final TranslationContext translationContext, final FacetHolder holder) {
+    public PropertyValidateFacetViaMethod(
+            final Method method,
+    		final TranslationContext translationContext,
+    		final FacetHolder holder) {
         super(holder);
         this.method = method;
-        this.translationService = translationService;
         this.translationContext = translationContext;
     }
 
@@ -68,7 +67,7 @@ public class PropertyValidateFacetViaMethod extends PropertyValidateFacetAbstrac
         }
         if(returnValue instanceof TranslatableString) {
             final TranslatableString ts = (TranslatableString) returnValue;
-            return ts.translate(translationService, translationContext);
+            return ts.translate(getTranslationService(), translationContext);
         }
         return null;
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/validating/method/PropertyValidateFacetViaMethodFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/validating/method/PropertyValidateFacetViaMethodFactory.java
index 6dadc08..82e5e50 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/validating/method/PropertyValidateFacetViaMethodFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/validating/method/PropertyValidateFacetViaMethodFactory.java
@@ -67,12 +67,11 @@ extends MethodPrefixBasedFacetFactoryAbstract  {
         processMethodContext.removeMethod(validateMethod);
 
         val facetHolder = processMethodContext.getFacetHolder();
-        val translationService = getTranslationService();
         // sadness: same as in TranslationFactory
         val translationContext = TranslationContext.forTranslationContextHolder(facetHolder.getFeatureIdentifier());
         addFacet(
                 new PropertyValidateFacetViaMethod(
-                        validateMethod, translationService, translationContext, facetHolder));
+                        validateMethod, translationContext, facetHolder));
     }
 
 
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 50bc556..53767b5 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.described.DescribedAsFacet;
 import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
+import org.apache.isis.core.metamodel.facets.all.i8n.HasTranslation;
 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;
@@ -131,13 +132,15 @@ public class LayoutFacetUtil {
             final HasDescribedAs hasDescribedAs,
             final FacetHolder facetHolder) {
 
-        val describedAsFacet = facetHolder.getFacet(DescribedAsFacet.class);
-        if(isDoOp(describedAsFacet)) {
-            final String describedAs = describedAsFacet.translated();
+        facetHolder.lookupNonFallbackFacet(DescribedAsFacet.class)
+        .filter(describedAsFacet->describedAsFacet instanceof HasTranslation)
+        .map(HasTranslation.class::cast)
+        .ifPresent(describedAsFacet->{
+            final String describedAs = describedAsFacet.preferredTranslated();
             if(!_Strings.isNullOrEmpty(describedAs)) {
                 hasDescribedAs.setDescribedAs(describedAs);
             }
-        }
+        });
     }
 
     public void setHiddenIfAny(
@@ -184,13 +187,15 @@ public class LayoutFacetUtil {
             final FacetHolder facetHolder) {
 
         facetHolder.lookupNonFallbackFacet(NamedFacet.class)
+        .filter(namedFacet->namedFacet instanceof HasTranslation)
+        .map(HasTranslation.class::cast)
         .filter(namedFacet->namedFacet.getSupportedNounForms().contains(NounForm.SINGULAR))
         .ifPresent(namedFacet->{
             final String named = namedFacet.translated(NounForm.SINGULAR);
             if(!_Strings.isNullOrEmpty(named)){
                 hasNamed.setNamed(named);
             }
-            final boolean escaped = namedFacet.escaped();
+            final boolean escaped = ((NamedFacet)namedFacet).escaped();
             if(!escaped) {
                 hasNamed.setNamedEscaped(escaped);
             }
@@ -215,6 +220,8 @@ public class LayoutFacetUtil {
             final FacetHolder facetHolder) {
 
         facetHolder.lookupNonFallbackFacet(NamedFacet.class)
+        .filter(namedFacet->namedFacet instanceof HasTranslation)
+        .map(HasTranslation.class::cast)
         .filter(namedFacet->namedFacet.getSupportedNounForms().contains(NounForm.PLURAL))
         .ifPresent(namedFacet->{
             val plural = namedFacet.translated(NounForm.PLURAL);
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/postprocessors/all/DeriveDescribedAsFromTypePostProcessor.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/postprocessors/all/DeriveDescribedAsFromTypePostProcessor.java
index 23a432e..caf75e4 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/postprocessors/all/DeriveDescribedAsFromTypePostProcessor.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/postprocessors/all/DeriveDescribedAsFromTypePostProcessor.java
@@ -60,8 +60,11 @@ extends ObjectSpecificationPostProcessorAbstract {
         }
         objectAction.getReturnType()
         .lookupNonFallbackFacet(DescribedAsFacet.class)
-        .ifPresent(specFacet -> FacetUtil.addFacet(new DescribedAsFacetOnMemberDerivedFromType(specFacet,
-                                    facetedMethodFor(objectAction))));
+        .ifPresent(specFacet -> FacetUtil.addFacetIfPresent(
+                DescribedAsFacetOnMemberDerivedFromType
+                .create(
+                        specFacet,
+                        facetedMethodFor(objectAction))));
     }
 
     @Override
@@ -70,13 +73,12 @@ extends ObjectSpecificationPostProcessorAbstract {
             return;
         }
         final ObjectSpecification paramSpec = parameter.getSpecification();
-        final DescribedAsFacet specFacet = paramSpec.getFacet(DescribedAsFacet.class);
-
-        //TODO: this ought to check if a do-op; if you come across this, you can probably change it (just taking smaller steps for now)
-        //if(existsAndIsDoOp(specFacet)) {
-        if(specFacet != null) {
-            FacetUtil.addFacet(new DescribedAsFacetOnParameterDerivedFromType(specFacet, peerFor(parameter)));
-        }
+        paramSpec.lookupNonFallbackFacet(DescribedAsFacet.class)
+        .ifPresent(describedAsFacet->{
+            FacetUtil.addFacetIfPresent(
+                    DescribedAsFacetOnParameterDerivedFromType
+                    .create(describedAsFacet, peerFor(parameter)));
+        });
     }
 
     @Override
@@ -95,8 +97,9 @@ extends ObjectSpecificationPostProcessorAbstract {
         }
         objectAssociation.getSpecification()
         .lookupNonFallbackFacet(DescribedAsFacet.class)
-        .ifPresent(specFacet -> FacetUtil.addFacet(new DescribedAsFacetOnMemberDerivedFromType(
-                                    specFacet, facetedMethodFor(objectAssociation))));
+        .ifPresent(specFacet -> FacetUtil.addFacetIfPresent(
+                DescribedAsFacetOnMemberDerivedFromType
+                .create(specFacet, facetedMethodFor(objectAssociation))));
     }
 
 }
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 7b2e5aa..bf1177e 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
@@ -21,9 +21,11 @@ package org.apache.isis.core.metamodel.postprocessors.all.i18n;
 
 import javax.inject.Inject;
 
+import org.apache.isis.commons.internal.functions._Functions;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.all.described.DescribedAsFacet;
+import org.apache.isis.core.metamodel.facets.all.i8n.HasTranslation;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.core.metamodel.postprocessors.ObjectSpecificationPostProcessorAbstract;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
@@ -77,11 +79,17 @@ extends ObjectSpecificationPostProcessorAbstract {
     private void memoizeTranslations(final FacetHolder facetHolder) {
         facetHolder
             .lookupFacet(NamedFacet.class)
-            .ifPresent(NamedFacet::memoizeTranslations);
+            .map(NamedFacet::getSpecialization)
+            .ifPresent(specialization->specialization
+                    .accept(  HasTranslation::memoizeTranslations,
+                              _Functions.noopConsumer()));
 
         facetHolder
             .lookupFacet(DescribedAsFacet.class)
-            .ifPresent(DescribedAsFacet::memoizeTranslations);
+            .map(DescribedAsFacet::getSpecialization)
+            .ifPresent(specialization->specialization
+                    .accept(  HasTranslation::memoizeTranslations,
+                              _Functions.noopConsumer()));
 
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ManagedObjects.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ManagedObjects.java
index 0acee80..c8e1935 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ManagedObjects.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ManagedObjects.java
@@ -35,8 +35,11 @@ import javax.annotation.Nullable;
 
 import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.i18n.TranslatableString;
+import org.apache.isis.applib.services.i18n.TranslationContext;
 import org.apache.isis.applib.services.repository.EntityState;
 import org.apache.isis.commons.collections.Can;
+import org.apache.isis.commons.functional.Result;
 import org.apache.isis.commons.internal.assertions._Assert;
 import org.apache.isis.commons.internal.base._NullSafe;
 import org.apache.isis.commons.internal.base._Objects;
@@ -77,7 +80,7 @@ public final class ManagedObjects {
     // -- CATEGORISATION
 
     /** is null or has neither an ObjectSpecification and a value (pojo) */
-    public static boolean isNullOrUnspecifiedOrEmpty(@Nullable ManagedObject adapter) {
+    public static boolean isNullOrUnspecifiedOrEmpty(@Nullable final ManagedObject adapter) {
         if(adapter==null || adapter==ManagedObject.unspecified()) {
             return true;
         }
@@ -85,7 +88,7 @@ public final class ManagedObjects {
     }
 
     /** whether has at least a spec */
-    public static boolean isSpecified(@Nullable ManagedObject adapter) {
+    public static boolean isSpecified(@Nullable final ManagedObject adapter) {
         return adapter!=null && adapter!=ManagedObject.unspecified();
     }
 
@@ -93,40 +96,40 @@ public final class ManagedObjects {
      * @return whether the corresponding type can be mapped onto a REFERENCE (schema) or an Oid,
      * that is, the type is 'identifiable' (aka 'referencable' or 'bookmarkable')
      */
-    public static boolean isIdentifiable(@Nullable ManagedObject managedObject) {
+    public static boolean isIdentifiable(@Nullable final ManagedObject managedObject) {
         return spec(managedObject)
                 .map(ObjectSpecification::isIdentifiable)
                 .orElse(false);
     }
 
-    public static boolean isEntity(ManagedObject managedObject) {
+    public static boolean isEntity(final ManagedObject managedObject) {
         return spec(managedObject)
                 .map(ObjectSpecification::isEntity)
                 .orElse(false);
     }
 
-    public static boolean isValue(ManagedObject managedObject) {
+    public static boolean isValue(final ManagedObject managedObject) {
         return spec(managedObject)
                 .map(ObjectSpecification::isValue)
                 .orElse(false);
     }
 
-    public static Optional<String> getDomainType(ManagedObject managedObject) {
+    public static Optional<String> getDomainType(final ManagedObject managedObject) {
         return spec(managedObject)
                 .map(ObjectSpecification::getLogicalTypeName);
     }
 
     // -- IDENTIFICATION
 
-    public static Optional<ObjectSpecification> spec(@Nullable ManagedObject managedObject) {
+    public static Optional<ObjectSpecification> spec(@Nullable final ManagedObject managedObject) {
         return isSpecified(managedObject) ? Optional.of(managedObject.getSpecification()) : Optional.empty();
     }
 
-    public static Optional<Bookmark> bookmark(@Nullable ManagedObject managedObject) {
+    public static Optional<Bookmark> bookmark(@Nullable final ManagedObject managedObject) {
         return isSpecified(managedObject) ? managedObject.getBookmark() : Optional.empty();
     }
 
-    public static Bookmark bookmarkElseFail(@Nullable ManagedObject managedObject) {
+    public static Bookmark bookmarkElseFail(@Nullable final ManagedObject managedObject) {
         return bookmark(managedObject)
                 .orElseThrow(()->_Exceptions.illegalArgument("cannot identify %s", managedObject));
     }
@@ -136,12 +139,12 @@ public final class ManagedObjects {
      * @return optionally a String representing a reference to the <em>identifiable</em>
      * {@code managedObject}, usually made up of the object's type and its ID.
      */
-    public static Optional<String> stringify(@Nullable ManagedObject managedObject) {
+    public static Optional<String> stringify(@Nullable final ManagedObject managedObject) {
         return bookmark(managedObject)
                 .map(Bookmark::stringify);
     }
 
-    public static String stringifyElseFail(@Nullable ManagedObject managedObject) {
+    public static String stringifyElseFail(@Nullable final ManagedObject managedObject) {
         return stringify(managedObject)
                 .orElseThrow(()->_Exceptions.illegalArgument("cannot stringify %s", managedObject));
     }
@@ -154,14 +157,14 @@ public final class ManagedObjects {
      * {@code managedObject}, made of the form &lt;object-type&gt; &lt;separator&gt; &lt;object-id&gt;.
      */
     public static Optional<String> stringify(
-            @Nullable ManagedObject managedObject,
+            @Nullable final ManagedObject managedObject,
             @NonNull final String separator) {
         return bookmark(managedObject)
                 .map(oid->oid.getLogicalTypeName() + separator + oid.getIdentifier());
     }
 
     public static String stringifyElseFail(
-            @Nullable ManagedObject managedObject,
+            @Nullable final ManagedObject managedObject,
             @NonNull final String separator) {
         return stringify(managedObject, separator)
                 .orElseThrow(()->_Exceptions.illegalArgument("cannot stringify %s", managedObject));
@@ -170,11 +173,11 @@ public final class ManagedObjects {
 
     // -- COMPARE UTILITIES
 
-    public static int compare(@Nullable ManagedObject p, @Nullable ManagedObject q) {
+    public static int compare(@Nullable final ManagedObject p, @Nullable final ManagedObject q) {
         return NATURAL_NULL_FIRST.compare(p, q);
     }
 
-    public static Comparator<ManagedObject> orderingBy(ObjectAssociation sortProperty, boolean ascending) {
+    public static Comparator<ManagedObject> orderingBy(final ObjectAssociation sortProperty, final boolean ascending) {
 
         final Comparator<ManagedObject> comparator = ascending
                 ? NATURAL_NULL_FIRST
@@ -193,7 +196,7 @@ public final class ManagedObjects {
     private static final Comparator<ManagedObject> NATURAL_NULL_FIRST = new Comparator<ManagedObject>(){
         @SuppressWarnings({"rawtypes" })
         @Override
-        public int compare(@Nullable ManagedObject p, @Nullable ManagedObject q) {
+        public int compare(@Nullable final ManagedObject p, @Nullable final ManagedObject q) {
             val pPojo = UnwrapUtil.single(p);
             val qPojo = UnwrapUtil.single(q);
             if(pPojo instanceof Comparable && qPojo instanceof Comparable) {
@@ -216,7 +219,7 @@ public final class ManagedObjects {
     // -- COPY UTILITIES
 
     @Nullable
-    public static ManagedObject copyIfClonable(@Nullable ManagedObject adapter) {
+    public static ManagedObject copyIfClonable(@Nullable final ManagedObject adapter) {
 
         if(adapter==null) {
             return null;
@@ -238,7 +241,7 @@ public final class ManagedObjects {
 
     // -- DEFAULTS UTILITIES
 
-    public static ManagedObject emptyToDefault(boolean mandatory, @NonNull ManagedObject input) {
+    public static ManagedObject emptyToDefault(final boolean mandatory, @NonNull final ManagedObject input) {
         if(!isSpecified(input)) {
             return input;
         }
@@ -264,15 +267,15 @@ public final class ManagedObjects {
 
     // -- TITLE UTILITIES
 
-    public static String abbreviatedTitleOf(ManagedObject adapter, int maxLength, String suffix) {
+    public static String abbreviatedTitleOf(final ManagedObject adapter, final int maxLength, final String suffix) {
         return abbreviated(titleOf(adapter), maxLength, suffix);
     }
 
-    public static String titleOf(ManagedObject adapter) {
+    public static String titleOf(final ManagedObject adapter) {
         return adapter!=null?adapter.titleString():"";
     }
 
-    private static String abbreviated(final String str, final int maxLength, String suffix) {
+    private static String abbreviated(final String str, final int maxLength, final String suffix) {
         return str.length() < maxLength ? str : str.substring(0, maxLength - 3) + suffix;
     }
 
@@ -345,7 +348,7 @@ public final class ManagedObjects {
      */
     @Deprecated
     @SneakyThrows
-    public static void warnIfAttachedEntity(ManagedObject adapter, String logMessage) {
+    public static void warnIfAttachedEntity(final ManagedObject adapter, final String logMessage) {
         if(isNullOrUnspecifiedOrEmpty(adapter)) {
             return;
         }
@@ -358,7 +361,7 @@ public final class ManagedObjects {
     /**
      * eg. in order to prevent wrapping an object that is already wrapped
      */
-    public static void assertPojoNotManaged(@Nullable Object pojo) {
+    public static void assertPojoNotManaged(@Nullable final Object pojo) {
         // can do this check only when the pojo is not null, otherwise is always considered valid
         if(pojo==null) {
             return;
@@ -373,6 +376,40 @@ public final class ManagedObjects {
         }
     }
 
+    // -- IMPERATIVE TEXT UTILITY
+
+    public static Result<String> imperativeText(
+            final @Nullable ManagedObject object,
+            final @NonNull Method method,
+            final @Nullable TranslationContext translationContext) {
+
+        if(ManagedObjects.isNullOrUnspecifiedOrEmpty(object)) {
+            return Result.success(null);
+        }
+
+        val mmc = object.getSpecification().getMetaModelContext();
+
+        val result =  Result.of(()->{
+            final Object returnValue = ManagedObjects.InvokeUtil.invoke(method, object);
+            if(returnValue instanceof String) {
+                return (String) returnValue;
+            }
+            if(returnValue instanceof TranslatableString) {
+                final TranslatableString ts = (TranslatableString) returnValue;
+                return ts.translate(mmc.getTranslationService(), translationContext);
+            }
+            return null;
+        });
+
+        if(result.isFailure()) {
+            val isUnitTesting = mmc.getSystemEnvironment().isUnitTesting();
+            if(!isUnitTesting) {
+                log.warn("imperative text failure (context: {})", translationContext, result.getFailure().get());
+            }
+        }
+
+        return result;
+    }
 
     // -- ENTITY UTILITIES
 
@@ -380,7 +417,7 @@ public final class ManagedObjects {
     public static final class EntityUtil {
 
         @NonNull
-        public static Optional<PersistenceStandard> getPersistenceStandard(@Nullable ManagedObject adapter) {
+        public static Optional<PersistenceStandard> getPersistenceStandard(@Nullable final ManagedObject adapter) {
             if(adapter==null) {
                 return Optional.empty();
             }
@@ -398,7 +435,7 @@ public final class ManagedObjects {
         }
 
         @NonNull
-        public static EntityState getEntityState(@Nullable ManagedObject adapter) {
+        public static EntityState getEntityState(@Nullable final ManagedObject adapter) {
             if(isNullOrUnspecifiedOrEmpty(adapter)) {
                 return EntityState.NOT_PERSISTABLE;
             }
@@ -417,21 +454,21 @@ public final class ManagedObjects {
             return entityFacet.getEntityState(pojo);
         }
 
-        public static void persistInCurrentTransaction(ManagedObject managedObject) {
+        public static void persistInCurrentTransaction(final ManagedObject managedObject) {
             requiresEntity(managedObject);
             val spec = managedObject.getSpecification();
             val entityFacet = spec.getFacet(EntityFacet.class);
             entityFacet.persist(spec, managedObject.getPojo());
         }
 
-        public static void destroyInCurrentTransaction(ManagedObject managedObject) {
+        public static void destroyInCurrentTransaction(final ManagedObject managedObject) {
             requiresEntity(managedObject);
             val spec = managedObject.getSpecification();
             val entityFacet = spec.getFacet(EntityFacet.class);
             entityFacet.delete(spec, managedObject.getPojo());
         }
 
-        public static void requiresEntity(ManagedObject managedObject) {
+        public static void requiresEntity(final ManagedObject managedObject) {
             if(isNullOrUnspecifiedOrEmpty(managedObject)) {
                 throw _Exceptions.illegalArgument("requires an entity object but got null, unspecified or empty");
             }
@@ -449,7 +486,7 @@ public final class ManagedObjects {
          * @throws AssertionError if managedObject is a detached entity
          */
         @NonNull
-        public static ManagedObject requiresAttached(@NonNull ManagedObject managedObject) {
+        public static ManagedObject requiresAttached(@NonNull final ManagedObject managedObject) {
             val entityState = EntityUtil.getEntityState(managedObject);
             if(entityState.isPersistable()) {
                 // ensure we have an attached entity
@@ -463,7 +500,7 @@ public final class ManagedObjects {
         }
 
         @Nullable
-        public static ManagedObject reattach(@Nullable ManagedObject managedObject) {
+        public static ManagedObject reattach(@Nullable final ManagedObject managedObject) {
             if(isNullOrUnspecifiedOrEmpty(managedObject)) {
                 return managedObject;
             }
@@ -500,8 +537,8 @@ public final class ManagedObjects {
         }
 
         public static void requiresWhenFirstIsBookmarkableSecondIsAttached(
-                ManagedObject first,
-                ManagedObject second) {
+                final ManagedObject first,
+                final ManagedObject second) {
 
             if(!ManagedObjects.isIdentifiable(first) || !ManagedObjects.isSpecified(second)) {
                 return;
@@ -521,16 +558,16 @@ public final class ManagedObjects {
 
         // -- SHORTCUTS
 
-        public static boolean isAttached(@Nullable ManagedObject adapter) {
+        public static boolean isAttached(@Nullable final ManagedObject adapter) {
             return EntityUtil.getEntityState(adapter).isAttached();
         }
 
-        public static boolean isDetachedOrRemoved(@Nullable ManagedObject adapter) {
+        public static boolean isDetachedOrRemoved(@Nullable final ManagedObject adapter) {
             return EntityUtil.getEntityState(adapter).isDetachedOrRemoved();
         }
 
         /** only supported by JDO - always false with JPA */
-        public static boolean isRemoved(@Nullable ManagedObject adapter) {
+        public static boolean isRemoved(@Nullable final ManagedObject adapter) {
             return EntityUtil.getEntityState(adapter).isRemoved();
         }
 
@@ -541,7 +578,7 @@ public final class ManagedObjects {
     @UtilityClass
     public static final class VisibilityUtil {
 
-        public static Predicate<? super ManagedObject> filterOn(InteractionInitiatedBy interactionInitiatedBy) {
+        public static Predicate<? super ManagedObject> filterOn(final InteractionInitiatedBy interactionInitiatedBy) {
             return $->ManagedObjects.VisibilityUtil.isVisible($, interactionInitiatedBy);
         }
 
@@ -594,8 +631,8 @@ public final class ManagedObjects {
          * @param interactionInitiatedBy
          */
         public static boolean isVisible(
-                ManagedObject adapter,
-                InteractionInitiatedBy interactionInitiatedBy) {
+                final ManagedObject adapter,
+                final InteractionInitiatedBy interactionInitiatedBy) {
 
             if(isNullOrUnspecifiedOrEmpty(adapter)) {
                 // a choices list could include a null (eg example in ToDoItems#choices1Categorized()); want to show as "visible"
@@ -659,27 +696,27 @@ public final class ManagedObjects {
             return invokeWithPPM(ppmConstructor, method, adapter, argumentAdapters, Collections.emptyList());
         }
 
-        public static void invokeAll(Iterable<Method> methods, final ManagedObject adapter) {
+        public static void invokeAll(final Iterable<Method> methods, final ManagedObject adapter) {
             MethodUtil.invoke(methods, UnwrapUtil.single(adapter));
         }
 
-        public static Object invoke(Method method, ManagedObject adapter) {
+        public static Object invoke(final Method method, final ManagedObject adapter) {
             return MethodExtensions.invoke(method, UnwrapUtil.single(adapter));
         }
 
-        public static Object invoke(Method method, ManagedObject adapter, Object arg0) {
+        public static Object invoke(final Method method, final ManagedObject adapter, final Object arg0) {
             return MethodExtensions.invoke(method, UnwrapUtil.single(adapter), new Object[] {arg0});
         }
 
-        public static Object invoke(Method method, ManagedObject adapter, Can<ManagedObject> argumentAdapters) {
+        public static Object invoke(final Method method, final ManagedObject adapter, final Can<ManagedObject> argumentAdapters) {
             return MethodExtensions.invoke(method, UnwrapUtil.single(adapter), UnwrapUtil.multipleAsArray(argumentAdapters));
         }
 
-        public static Object invoke(Method method, ManagedObject adapter, ManagedObject arg0Adapter) {
+        public static Object invoke(final Method method, final ManagedObject adapter, final ManagedObject arg0Adapter) {
             return invoke(method, adapter, UnwrapUtil.single(arg0Adapter));
         }
 
-        public static Object invoke(Method method, ManagedObject adapter, ManagedObject[] argumentAdapters) {
+        public static Object invoke(final Method method, final ManagedObject adapter, final ManagedObject[] argumentAdapters) {
             return MethodExtensions.invoke(method, UnwrapUtil.single(adapter), UnwrapUtil.multipleAsArray(argumentAdapters));
         }
 
@@ -691,7 +728,7 @@ public final class ManagedObjects {
          * <li>if the method declares parameters but arguments are missing, then will provide 'null' defaults for these.</li>
          * </ul>
          */
-        public static Object invokeAutofit(Method method, ManagedObject adapter) {
+        public static Object invokeAutofit(final Method method, final ManagedObject adapter) {
             return invoke(method, adapter, new ManagedObject[method.getParameterTypes().length]);
         }
 
@@ -759,7 +796,7 @@ public final class ManagedObjects {
 
         }
 
-        private static Iterator<? extends ManagedObject> argIteratorFrom(Can<? extends ManagedObject> pendingArgs) {
+        private static Iterator<? extends ManagedObject> argIteratorFrom(final Can<? extends ManagedObject> pendingArgs) {
             return pendingArgs!=null ? pendingArgs.iterator() : Collections.emptyIterator();
         }
 
@@ -791,7 +828,7 @@ public final class ManagedObjects {
         }
 
         @Nullable
-        public static String singleAsStringOrElse(@Nullable final ManagedObject adapter, @Nullable String orElse) {
+        public static String singleAsStringOrElse(@Nullable final ManagedObject adapter, @Nullable final String orElse) {
             final Object obj = UnwrapUtil.single(adapter);
             if (obj == null) {
                 return null;
@@ -870,4 +907,6 @@ public final class ManagedObjects {
     }
 
 
+
+
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectFeature.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectFeature.java
index 864beb9..fea4aea 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectFeature.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectFeature.java
@@ -50,13 +50,17 @@ public interface ObjectFeature extends Specification {
      * the name of this member.
      *
      * @see #getFeatureIdentifier()
+     * @deprecated TODO[ISIS-1720] must take ManagedObject as an argument
      */
+    @Deprecated
     String getName();
 
     /**
      * Returns a description of how the member is used - this complements the
      * help text.
+     * @deprecated TODO[ISIS-1720] must take ManagedObject as an argument
      */
+    @Deprecated
     @Override
     String getDescription();
 
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 475d675..07cde20 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,7 +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.described.DescribedAsFacet;
-import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
+import org.apache.isis.core.metamodel.facets.all.i8n.HasTranslation;
 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,10 +134,16 @@ implements ObjectActionParameter, HasFacetHolder {
 
     @Override
     public String getName() {
-        final NamedFacet facet = getFacet(NamedFacet.class);
-        if (facet != null
-                && facet.translated(NounForm.SINGULAR) != null) {
-            return facet.translated(NounForm.SINGULAR);
+
+        // assuming parameters don't have imperative naming support
+        val name = lookupFacet(NamedFacet.class)
+        .filter(namedFacet->namedFacet instanceof HasTranslation)
+        .map(HasTranslation.class::cast)
+        .map(HasTranslation::preferredTranslated)
+        .orElse(null);
+
+        if (name!=null) {
+            return name;
         }
         val singularName = getSpecification().getSingularName();
         val parameters = getAction().getParameters(this::equalsShortIdentifier);
@@ -157,9 +163,12 @@ implements ObjectActionParameter, HasFacetHolder {
 
     @Override
     public String getDescription() {
-        final DescribedAsFacet facet = getFacet(DescribedAsFacet.class);
-        final String description = facet.translated();
-        return description == null ? "" : description;
+        // assuming parameters don't have imperative description support
+        return lookupFacet(DescribedAsFacet.class)
+        .filter(describedAsFacet->describedAsFacet instanceof HasTranslation)
+        .map(HasTranslation.class::cast)
+        .map(HasTranslation::preferredTranslated)
+        .orElse("");
     }
 
     public Consent isUsable() {
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 3cb9e0b..411cd28 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
@@ -123,19 +123,32 @@ implements ObjectMember, HasMetaModelContext, HasFacetHolder {
      */
     @Override
     public String getName() {
+
+        final ManagedObject owner = null; //TODO[ISIS-1720] must take ManagedObject (owner) as an argument
+
         val namedFacet = getFacet(NamedFacet.class);
 
         if(namedFacet==null) {
-            throw _Exceptions.unrecoverableFormatted("no namedFacet preset on %s", getFeatureIdentifier());
+            throw _Exceptions.unrecoverableFormatted("no NamedFacet preset on %s", getFeatureIdentifier());
         }
 
-        return namedFacet.preferredTranslated();
+        return namedFacet
+            .getSpecialization()
+            .fold(  textFacet->textFacet.preferredTranslated(),
+                    textFacet->textFacet.textElseNull(owner));
     }
 
     @Override
     public String getDescription() {
-        val describedAsFacet = getFacet(DescribedAsFacet.class);
-        return describedAsFacet.translated();
+
+        final ManagedObject owner = null; //TODO[ISIS-1720] must take ManagedObject (owner) as an argument
+
+        return lookupFacet(DescribedAsFacet.class)
+        .map(DescribedAsFacet::getSpecialization)
+        .map(specialization->specialization
+                .fold(textFacet->textFacet.preferredTranslated(),
+                      textFacet->textFacet.textElseNull(owner)))
+        .orElse(null);
     }
 
     @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 2f11cd0..bf31768 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
@@ -466,47 +466,28 @@ implements ObjectSpecification {
 
         return thisClass == otherClass
                 || otherClass.isAssignableFrom(thisClass);
-
-//XXX legacy of ...
-//
-//        for (val interfaceSpec : interfaces()) {
-//            if (interfaceSpec.isOfType(other)) {
-//                return true;
-//            }
-//        }
-//
-//        // this is a bit of a workaround; the metamodel doesn't have the interfaces for enums.
-//        val correspondingClass = getCorrespondingClass();
-//        val possibleSupertypeClass = other.getCorrespondingClass();
-//        if(correspondingClass != null && possibleSupertypeClass != null &&
-//                Enum.class.isAssignableFrom(correspondingClass) && possibleSupertypeClass.isInterface()) {
-//            if(possibleSupertypeClass.isAssignableFrom(correspondingClass)) {
-//                return true;
-//            }
-//        }
-//
-//        val superclassSpec = superclass();
-//        return superclassSpec != null && superclassSpec.isOfType(other);
     }
 
     // -- NAME, DESCRIPTION, PERSISTABILITY
 
     @Override
     public String getSingularName() {
-        val namedFacet = getFacet(NamedFacet.class);
-        return namedFacet != null
-                && namedFacet.getSupportedNounForms().contains(NounForm.SINGULAR)
-                        ? namedFacet.translated(NounForm.SINGULAR)
-                        : this.getFullIdentifier();
+        return lookupFacet(NamedFacet.class)
+            .map(NamedFacet::getSpecialization)
+            .map(specialization->specialization
+                    .fold(  textFacet->textFacet.translatedElseNull(NounForm.SINGULAR),
+                            textFacet->textFacet.textElseNull(null)))
+            .orElseGet(this::getFullIdentifier);
     }
 
     @Override
     public String getPluralName() {
-        val namedFacet = getFacet(NamedFacet.class);
-        return namedFacet != null
-                && namedFacet.getSupportedNounForms().contains(NounForm.PLURAL)
-                        ? namedFacet.translated(NounForm.PLURAL)
-                        : this.getFullIdentifier();
+        return lookupFacet(NamedFacet.class)
+                .map(NamedFacet::getSpecialization)
+                .map(specialization->specialization
+                        .fold(  textFacet->textFacet.translatedElseNull(NounForm.PLURAL),
+                                textFacet->textFacet.textElseNull(null)))
+                .orElseGet(this::getFullIdentifier);
     }
 
     /**
@@ -515,10 +496,12 @@ implements ObjectSpecification {
      */
     @Override
     public String getDescription() {
-        val describedAsFacet = getFacet(DescribedAsFacet.class);
-        return describedAsFacet != null
-                ? _Strings.nullToEmpty(describedAsFacet.translated())
-                : "";
+        return lookupFacet(DescribedAsFacet.class)
+                .map(DescribedAsFacet::getSpecialization)
+                .map(specialization->specialization
+                        .fold(  textFacet->textFacet.preferredTranslated(),
+                                textFacet->textFacet.textElseNull(null)))
+                .orElse("");
     }
 
     /*
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 8fa93e8..2c81f46 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.HasTranslation;
 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;
@@ -58,7 +59,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(NounForm.PLURAL), is(equalTo("1st names")));
+        assertThat(((HasTranslation)facet).text(NounForm.PLURAL), is(equalTo("1st names")));
         assertThat(facet.escaped(), is(true));
     }
 
@@ -78,7 +79,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(NounForm.PLURAL), is(equalTo("1st names")));
+        assertThat(((HasTranslation)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 f127cd4..cab594f 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
@@ -41,6 +41,7 @@ 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.described.DescribedAsFacet;
+import org.apache.isis.core.metamodel.facets.all.i8n.HasTranslation;
 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;
@@ -296,7 +297,7 @@ extends AbstractFacetFactoryJUnit4TestCase {
                 assertNotNull(namedFacet);
                 assertTrue(namedFacet instanceof NamedFacetForDomainObjectLayoutAnnotation);
 
-                assertEquals("Name override", namedFacet.text(NounForm.SINGULAR));
+                assertEquals("Name override", ((HasTranslation)namedFacet).text(NounForm.SINGULAR));
 
                 expectNoMethodsRemoved();
             }
@@ -383,7 +384,7 @@ extends AbstractFacetFactoryJUnit4TestCase {
                 final NamedFacet namedFacet = facetHolder.getFacet(NamedFacet.class);
                 assertNotNull(namedFacet);
 
-                assertEquals("Customers Plural Form", namedFacet.translated(NounForm.PLURAL));
+                assertEquals("Customers Plural Form", ((HasTranslation)namedFacet).translated(NounForm.PLURAL));
 
                 expectNoMethodsRemoved();
             }
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/ident/title/TitleFacetViaMethodTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/ident/title/TitleFacetViaMethodTest.java
index 7eceeae..085bb20 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/ident/title/TitleFacetViaMethodTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/ident/title/TitleFacetViaMethodTest.java
@@ -30,15 +30,15 @@ import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
 import org.apache.isis.core.metamodel._testing.MetaModelContext_forTesting;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.object.title.methods.TitleFacetViaTitleMethod;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
 @RunWith(JMock.class)
 public class TitleFacetViaMethodTest {
 
@@ -64,19 +64,19 @@ public class TitleFacetViaMethodTest {
 
         metaModelContext = MetaModelContext_forTesting.builder()
                 .build();
-        
+
         pojo = new DomainObjectWithProblemInItsTitleMethod();
         mockFacetHolder = mockery.mock(FacetHolder.class);
         mockOwningAdapter = mockery.mock(ManagedObject.class);
         final Method iconNameMethod = DomainObjectWithProblemInItsTitleMethod.class.getMethod("title");
-        facet = new TitleFacetViaTitleMethod(iconNameMethod, null, null, mockFacetHolder);
+        facet = new TitleFacetViaTitleMethod(iconNameMethod, null, mockFacetHolder);
 
         mockery.checking(new Expectations() {
             {
-                
+
                 allowing(mockFacetHolder).getMetaModelContext();
                 will(returnValue(metaModelContext));
-                
+
                 allowing(mockOwningAdapter).getPojo();
                 will(returnValue(pojo));
             }
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 2281d56..da01255 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.HasTranslation;
 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;
@@ -54,7 +55,7 @@ public class NamedFacetForParameterLayoutAnnotationFactoryTest extends AbstractF
         final NamedFacet facet = facetedMethodParameter.getFacet(NamedFacet.class);
         assertThat(facet, is(notNullValue()));
         assertThat(facet, is(instanceOf(NamedFacetForParameterLayoutAnnotation.class)));
-        assertEquals(NAME, facet.text(NounForm.SINGULAR));
+        assertEquals(NAME, ((HasTranslation)facet).text(NounForm.SINGULAR));
         assertThat(facet.escaped(), is(true));
     }
 
@@ -73,8 +74,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(NounForm.SINGULAR), is(equalTo(NAME)));
-        assertEquals(NAME, facet.text(NounForm.SINGULAR));
+        assertThat(((HasTranslation)facet).text(NounForm.SINGULAR), is(equalTo(NAME)));
+        assertEquals(NAME, ((HasTranslation)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 5213642..850067d 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.HasTranslation;
 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;
@@ -111,7 +112,7 @@ extends AbstractFacetFactoryJUnit4TestCase {
         // then
         val namedFacet = facetedMethodParameter.getFacet(NamedFacet.class);
 
-        assertEquals("An Awesome Name", namedFacet.text(NounForm.SINGULAR));
+        assertEquals("An Awesome Name", ((HasTranslation)namedFacet).text(NounForm.SINGULAR));
 
     }
 
@@ -141,7 +142,7 @@ extends AbstractFacetFactoryJUnit4TestCase {
         // then
         val namedFacet = facetedMethodParameter.getFacet(NamedFacet.class);
         assertNotNull(namedFacet);
-        assertEquals("Even Better Name", namedFacet.text(NounForm.SINGULAR));
+        assertEquals("Even Better Name", ((HasTranslation)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 27130bb..4e3265c 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.HasTranslation;
 import org.apache.isis.core.metamodel.facets.all.i8n.NounForm;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
 
@@ -60,7 +61,7 @@ extends AbstractFacetFactoryTest {
         final NamedFacet facet = facetedMethod.getFacet(NamedFacet.class);
         assertThat(facet, is(notNullValue()));
         assertThat(facet, is(instanceOf(NamedFacetForPropertyLayoutAnnotation.class)));
-        assertThat(facet.text(NounForm.SINGULAR), is(equalTo("1st name")));
+        assertThat(((HasTranslation)facet).text(NounForm.SINGULAR), is(equalTo("1st name")));
         assertThat(facet.escaped(), is(true));
     }
 
@@ -86,7 +87,7 @@ extends AbstractFacetFactoryTest {
         final NamedFacet facet = facetedMethod.getFacet(NamedFacet.class);
         assertThat(facet, is(notNullValue()));
         assertThat(facet, is(instanceOf(NamedFacetForPropertyLayoutAnnotation.class)));
-        assertThat(facet.text(NounForm.SINGULAR), is(equalTo("1st name")));
+        assertThat(((HasTranslation)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/OneToManyAssociationDefaultTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/OneToManyAssociationDefaultTest.java
index ab8d392..de95fba 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
@@ -33,10 +33,12 @@ 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.commons.internal.base._Either;
 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.HasTranslation;
 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;
@@ -65,11 +67,13 @@ public class OneToManyAssociationDefaultTest {
     @Mock ObjectSpecification mockOwnerAdapterSpec;
     @Mock MessageService mockMessageService;
     @Mock FacetedMethod mockPeer;
-    @Mock NamedFacet mockNamedFacet;
+    @Mock NamedFacetStatic mockNamedFacet;
 
     private OneToManyAssociation association;
     private MetaModelContext_forTesting metaModelContext;
 
+    private static interface NamedFacetStatic extends NamedFacet, HasTranslation {};
+
     @Before
     public void setUp() {
 
@@ -133,6 +137,9 @@ public class OneToManyAssociationDefaultTest {
                 oneOf(mockPeer).getFacet(NamedFacet.class);
                 will(returnValue(mockNamedFacet));
 
+                allowing(mockNamedFacet).getSpecialization();
+                will(returnValue(_Either.left(mockNamedFacet)));
+
                 allowing(mockNamedFacet).preferredTranslated();
                 will(returnValue("My name"));
 
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 4b65520..13e4bff 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,7 +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.i8n.HasTranslation;
 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;
@@ -56,7 +56,7 @@ public class ObjectActionParameterAbstractTest_getId_and_getName {
     @Mock
     private TypedHolder actionParamPeer;
     @Mock
-    private NamedFacet namedFacet;
+    private NamedFacetStatic namedFacet;
 
     @Mock
     private ObjectSpecification stubSpecForString;
@@ -66,6 +66,8 @@ public class ObjectActionParameterAbstractTest_getId_and_getName {
     private ObjectActionParameter stubObjectActionParameterString2;
 
 
+    private static interface NamedFacetStatic extends NamedFacet, HasTranslation {};
+
     private static final class ObjectActionParameterAbstractToTest extends ObjectActionParameterAbstract {
         private ObjectActionParameterAbstractToTest(final int number, final ObjectActionDefault objectAction, final TypedHolder peer) {
             super(FeatureType.ACTION_PARAMETER_SCALAR, number, objectAction, peer);
@@ -137,7 +139,7 @@ public class ObjectActionParameterAbstractTest_getId_and_getName {
                 oneOf(actionParamPeer).getFacet(NamedFacet.class);
                 will(returnValue(namedFacet));
 
-                atLeast(1).of(namedFacet).translated(NounForm.SINGULAR);
+                atLeast(1).of(namedFacet).preferredTranslated();
                 will(returnValue("Some parameter name"));
             }
         });
@@ -155,7 +157,7 @@ public class ObjectActionParameterAbstractTest_getId_and_getName {
                 oneOf(actionParamPeer).getFacet(NamedFacet.class);
                 will(returnValue(namedFacet));
 
-                atLeast(1).of(namedFacet).translated(NounForm.SINGULAR);
+                atLeast(1).of(namedFacet).preferredTranslated();
                 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 27f7de2..cf4ce4f 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
@@ -53,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.HasTranslation;
 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;
@@ -308,7 +309,9 @@ 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(NounForm.SINGULAR);
+            // assuming services have immutable names (no imperative naming support)
+            String serviceName = ((HasTranslation)serviceSpec.getFacet(NamedFacet.class))
+                    .translated(NounForm.SINGULAR);
             serviceNameOrder.add(serviceName);
         }
         // then, any other services (eg due to misspellings, at the end)
@@ -378,7 +381,9 @@ public class MenuBarsServiceBS3 implements MenuBarsService {
                             ? layoutGroupFacet.getGroupId()
                             : null;
                     if(_Strings.isNullOrEmpty(serviceName)){
-                        serviceName = serviceSpec.getFacet(NamedFacet.class).translated(NounForm.SINGULAR);
+                        // assuming services have immutable names (no imperative naming support)
+                        serviceName = ((HasTranslation)serviceSpec.getFacet(NamedFacet.class))
+                                .translated(NounForm.SINGULAR);
                     }
                     return new ServiceAndAction(serviceName, serviceAdapter, objectAction);
                 });
diff --git a/regressiontests/stable-interact/src/test/java/org/apache/isis/testdomain/interact/NewParameterModelTest.java b/regressiontests/stable-interact/src/test/java/org/apache/isis/testdomain/interact/NewParameterModelTest.java
index d5f73dd..e93042b 100644
--- a/regressiontests/stable-interact/src/test/java/org/apache/isis/testdomain/interact/NewParameterModelTest.java
+++ b/regressiontests/stable-interact/src/test/java/org/apache/isis/testdomain/interact/NewParameterModelTest.java
@@ -32,6 +32,7 @@ import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.core.config.presets.IsisPresets;
 import org.apache.isis.core.metamodel.facets.all.described.DescribedAsFacet;
+import org.apache.isis.core.metamodel.facets.all.i8n.HasTranslation;
 import org.apache.isis.core.metamodel.facets.objectvalue.maxlen.MaxLengthFacet;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.testdomain.conf.Configuration_headless;
@@ -85,7 +86,7 @@ class NewParameterModelTest extends InteractionTestAbstract {
         assertNotNull(describedAsFacet);
 
         assertEquals(2, maxLengthFacet.value());
-        assertEquals("first", describedAsFacet.text());
+        assertEquals("first", ((HasTranslation)describedAsFacet).preferredText());
     }
 
     @Test
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 dd7159d..0a689da 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
@@ -86,7 +86,7 @@ public final class ActionUiMetaModel implements Serializable {
 
         this(   objectAction.getMemento(),
                 objectAction.getName(),
-                getDescription(objectAction).orElse(ObjectAction.Util.descriptionOf(objectAction)),
+                getDescription(actionHolder, objectAction).orElse(ObjectAction.Util.descriptionOf(objectAction)),
                 ObjectAction.Util.returnsBlobOrClob(objectAction),
                 objectAction.isPrototype(),
                 ObjectAction.Util.actionIdentifierFor(objectAction),
@@ -147,11 +147,15 @@ public final class ActionUiMetaModel implements Serializable {
     // -- DESCRIBED AS
 
     private static Optional<String> getDescription(
+            @NonNull final ManagedObject actionHolder,
             @NonNull final ObjectAction objectAction) {
 
-        val describedAsFacet = objectAction.getFacet(DescribedAsFacet.class);
-        return Optional.ofNullable(describedAsFacet)
-                .map(DescribedAsFacet::translated);
+        return objectAction.lookupFacet(DescribedAsFacet.class)
+        .map(DescribedAsFacet::getSpecialization)
+        .map(specialization->specialization
+                .fold(textFacet->textFacet.preferredTranslated(),
+                      textFacet->textFacet.textElseNull(actionHolder)));
+
     }
 
 }
diff --git a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModel.java b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModel.java
index 7f02606..8867e64 100644
--- a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModel.java
+++ b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModel.java
@@ -83,8 +83,8 @@ extends
     }
 
     static EntityCollectionModelStandalone createStandalone(
-            ManagedObject collectionAsAdapter,
-            ActionModel actionModel) {
+            final ManagedObject collectionAsAdapter,
+            final ActionModel actionModel) {
         return EntityCollectionModelStandalone.forActionModel(collectionAsAdapter, actionModel);
     }
 
@@ -107,6 +107,7 @@ extends
 
     ObjectSpecification getTypeOfSpecification();
     ObjectMember getMetaModel();
+    ManagedObject getParentObject();
 
     /**
      * Returns all actions that are associated with this collection,
diff --git a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModelDummy.java b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModelDummy.java
index 36f7bd0..83df7ca 100644
--- a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModelDummy.java
+++ b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModelDummy.java
@@ -68,4 +68,9 @@ extends EntityCollectionModelAbstract {
         throw _Exceptions.unsupportedOperation();
     }
 
+    @Override
+    public ManagedObject getParentObject() {
+        throw _Exceptions.unsupportedOperation();
+    }
+
 }
diff --git a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModelParented.java b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModelParented.java
index 4e53cd4..77fb781 100644
--- a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModelParented.java
+++ b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/EntityCollectionModelParented.java
@@ -182,9 +182,14 @@ implements
         return entityModel.memento();
     }
 
+    @Override
+    public ManagedObject getParentObject() {
+        return getManagedCollection().getOwner();
+    }
+
     // -- ACTION ORDER
 
-    private int deweyOrderCompare(ObjectAction a, ObjectAction b) {
+    private int deweyOrderCompare(final ObjectAction a, final ObjectAction b) {
         val seqA = a.lookupFacet(LayoutOrderFacet.class)
             .map(LayoutOrderFacet::getSequence)
             .orElse("1");
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 efa9940..878b6b3 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
@@ -96,9 +96,13 @@ extends EntityCollectionModelAbstract {
 
     @Override
     public String getName() {
-        return getTypeOfSpecification().lookupFacet(NamedFacet.class)
-                .map(namedFacet->namedFacet.translated(NounForm.PLURAL))
-                .orElse(getIdentifier().getMemberLogicalName());
+        return getTypeOfSpecification()
+            .lookupFacet(NamedFacet.class)
+            .map(NamedFacet::getSpecialization)
+            .map(specialization->specialization
+                    .fold(namedFacet->namedFacet.translated(NounForm.PLURAL),
+                          namedFacet->namedFacet.textElseNull(actionModel.getOwner())))
+            .orElse(getIdentifier().getMemberLogicalName());
     }
 
     @Override
@@ -106,5 +110,9 @@ extends EntityCollectionModelAbstract {
         return actionModel.getMetaModel();
     }
 
+    @Override
+    public ManagedObject getParentObject() {
+        return actionModel.getOwner();
+    }
 
 }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java
index 950c29a..4aabc68 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java
@@ -58,8 +58,6 @@ import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.
 import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.columns.ObjectAdapterToggleboxColumn;
 import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract;
 
-import static org.apache.isis.commons.internal.base._With.mapIfPresentElse;
-
 import lombok.NonNull;
 import lombok.val;
 
@@ -276,7 +274,7 @@ implements CollectionCountProvider {
         if(parentSpec == null) {
             return _Predicates.alwaysTrue();
         }
-        return (ObjectAssociation property) -> {
+        return (final ObjectAssociation property) -> {
                 val hiddenFacet = property.getFacet(HiddenFacet.class);
                 if(hiddenFacet == null) {
                     return true;
@@ -293,18 +291,25 @@ implements CollectionCountProvider {
 
     private ObjectAdapterPropertyColumn createObjectAdapterPropertyColumn(final ObjectAssociation property) {
 
+        val collectionModel = getModel();
+
         final NamedFacet facet = property.getFacet(NamedFacet.class);
         final boolean escaped = facet == null || facet.escaped();
 
         final String parentTypeName = property.getOnType().getLogicalTypeName();
-        final String describedAs = mapIfPresentElse(property.getFacet(DescribedAsFacet.class),
-                DescribedAsFacet::translated, null);
+
+        final String describedAs = property.lookupFacet(DescribedAsFacet.class)
+                .map(DescribedAsFacet::getSpecialization)
+                .map(specialization->specialization
+                        .fold(textFacet->textFacet.preferredTranslated(),
+                              textFacet->textFacet.textElseNull(collectionModel.getParentObject())))
+                .orElse(null);
 
         val commonContext = super.getCommonContext();
 
         return new ObjectAdapterPropertyColumn(
                 commonContext,
-                getModel().getVariant(),
+                collectionModel.getVariant(),
                 Model.of(property.getName()),
                 property.getId(),
                 property.getId(),