content: /*
   *  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.causeway.viewer.wicket.viewer.integration;
  
  import java.util.Locale;
  import java.util.MissingResourceException;
  import java.util.Optional;
  
  import jakarta.inject.Inject;
  
  import org.apache.wicket.Component;
  import org.apache.wicket.Localizer;
  import org.apache.wicket.MarkupContainer;
  import org.apache.wicket.Page;
  import org.apache.wicket.markup.html.form.Form;
  import org.apache.wicket.markup.html.panel.Panel;
  import org.apache.wicket.model.IModel;
  
  import org.apache.causeway.applib.services.i18n.TranslationContext;
  import org.apache.causeway.applib.services.i18n.TranslationService;
  import org.apache.causeway.applib.services.iactnlayer.InteractionLayerTracker;
  import org.apache.causeway.applib.services.iactnlayer.InteractionService;
  import org.apache.causeway.commons.internal.base._Casts;
  import org.apache.causeway.commons.internal.base._Strings;
  import org.apache.causeway.viewer.wicket.viewer.wicketapp.CausewayWicketApplication;
  
  /**
   * Implementation integrates Causeway' own i18n support ({@link TranslationService}) with Wicket's equivalent i18n
   * mechanism (the {@link Localizer} singleton).
   */
  public class LocalizerForCauseway extends Localizer {
  
      @Inject private InteractionLayerTracker interactionLayerTracker;
      @Inject private InteractionService interactionService;
      @Inject private TranslationService translationService;
  
      /**
       * Uses Causeway' {@link TranslationService} to translate the key, but falls back to Wicket's own implementation
       * if no translation is available.
       */
      @Override
      public String getString(
              final String key,
              final Component component,
              final IModel<?> model,
              final Locale locale,
              final String style,
              final IModel<String> defaultValue)
                      throws MissingResourceException {
  
          final String translated = translate(key, component);
          if(!_Strings.isNullOrEmpty(translated) && !translated.equals(key)) {
              return translated;
          }
          return super.getString(key, component, model, locale, style, defaultValue);
      }
  
      protected String translate(final String key, final Component component) {
          final Class<?> contextClass = determineContextClassElse(component, CausewayWicketApplication.class);
          final TranslationContext context = TranslationContext.forClassName(contextClass);
          if(interactionLayerTracker.isInInteraction()) {
              return translate(key, context);
          } else {
              return interactionService.callAnonymous(()->translate(key, context));
          }
      }
  
      private Class<?> determineContextClassElse(final Component component, final Class<?> fallback) {
  
          if(component==null) {
              return fallback;
          }
  
          // special case
          if(component instanceof org.wicketstuff.select2.Select2Choice ||
                  component instanceof org.wicketstuff.select2.Select2MultiChoice) {
              return component.getClass();
          }
  
          return pageElseSignificantParentOf(component)
                  .map(parentComponent->enclosing(parentComponent.getClass()))
                  .orElse(_Casts.uncheckedCast(fallback));
      }
  
      private Optional<Component> pageElseSignificantParentOf(final Component component) {
          final Component page = pageOf(component);
          if (page != null) {
              return Optional.of(page);
          }
          return parentFormOrPanelOf(component);
      }
  
      /**
       * Search up this component instance's hierarchy looking for containing page.
       */
      private Component pageOf(final Component component) {
          if(component instanceof Page) {
              return component;
          }
          final MarkupContainer parent = component.getParent();
          if(parent != null) {
              return pageOf(parent);
          }
          return component;
      }
  
      /**
       * Search up this component instance's hierarchy, and use the first form or panel that is a parent
       * of this component.
       */
      private Optional<Component> parentFormOrPanelOf(final Component component) {
          if(component instanceof Form || component instanceof Panel) {
              return Optional.of(component);
          }
          var parent = component.getParent();
          if(parent != null) {
              return parentFormOrPanelOf(parent);
          }
          return Optional.empty();
      }
  
      private Class<?> enclosing(final Class<?> cls) {
          Class<?> enclosingClass = cls.getEnclosingClass();
          return enclosingClass != null? enclosing(enclosingClass): cls;
      }
  
      private String translate(final String key, final TranslationContext context) {
          return translationService.translate(context, key);
      }
  
  }
  