silverstripe / developer-docs

Developer documentation for Silverstripe CMS
Other
6 stars 61 forks source link

Document how to unset and change the fallback locale for i18n #292

Open RVXD opened 1 year ago

RVXD commented 1 year ago

Affected Version

Framework 4.13.10 and probably up

Description

Note: See comments for a clear indication of what needs to be documented.

When translating keywords in templates with <%t MyClass.MyKeyword "My Default Value" %> and the default language is not EN then the Default Value will not be displayed but instead the value of the English language file will be shown if the MyClass.MyKeyword is not in the language yml file of the (non english) default language.

It should fall back to the default - not the English version. So not expected behavior.

Steps to Reproduce

Create nl.yml file and add nothing Create en.yml file and add: MyClass.MyKeyword: 'MY ENGLISH VALUE' Set locale to NL and test in template with <%t MyClass.MyKeyword "My Default Value" %> 'MY ENGLISH VALUE' will be shown instead of 'My Default Value'

Fix with Injector

Override the FallbackLocales value of Symfony\Component\Translation\TranslatorInterface this should be set to the default language and not to English. In this example the setFallbackLocales language is set to dutch language NL.

---
Name: resettranslatorinterface
---
SilverStripe\Core\Injector\Injector:
  Symfony\Component\Translation\TranslatorInterface: false
---
Name: overridetranslatorinterface
---
SilverStripe\Core\Injector\Injector:
  Symfony\Component\Translation\TranslatorInterface:
    class: Symfony\Component\Translation\Translator
    constructor:
      0: 'en'
      1: null
      2: '`TEMP_PATH`'
    properties:
      ConfigCacheFactory: '%$Symfony\Component\Config\ConfigCacheFactoryInterface'
    calls:
      FallbackLocales: [ setFallbackLocales, [['nl']]]
      Loader: [ addLoader, ['ss', '%$Symfony\Component\Translation\Loader\LoaderInterface' ]]

Fix with _config.php

$provider = Injector::inst()->create(MessageProvider::class);
$provider->getTranslator()->setFallbackLocales(['nl']);
GuySartorelli commented 1 year ago

i18n falls back to whatever the fallback locale is defined as. By default this is English. We can't assume that the fallback locale should be the current locale... because the whole point is that you're falling back to it if the current locale doesn't have a localisation for the given string.

If you don't want to fall back to English, you can declare that there is no fallback locale. That can be done with this YAML snippet:

---
Name: i18nSetFallbackLocale
After: '#i18nMessages'
---
SilverStripe\Core\Injector\Injector:
  # for CMS 4 use Symfony\Component\Translation\TranslatorInterface:
  Symfony\Contracts\Translation\TranslatorInterface:
    calls:
      FallbackLocales: null

If you do want an explicit fallback locale that is not en, the code samples you provide to set the fallback locale(s) can be simplified as follows:

Set with yaml

Note we only need to set a new locale by calling setFallbackLocales again. There's no need to unset anything - though if you wanted to unset the existing call first you could simply set the FallbackLocales call to null first, then set it to the correct call in a second config set. This is a very low-cost call though so it's simpler to just call it a second time with your chosen value.

---
Name: i18nSetFallbackLocale
After: '#i18nMessages'
---
SilverStripe\Core\Injector\Injector:
  # for CMS 4 use Symfony\Component\Translation\TranslatorInterface:
  Symfony\Contracts\Translation\TranslatorInterface:
    calls:
      FallbackLocales2: [ setFallbackLocales, [['nl']]]

Set in php

Note we only need the TranslatorInterface singleton, rather than instantiating a new MessageProvider.

use SilverStripe\Core\Injector\Injector;
// for CMS 4 use Symfony\Component\Translation\TranslatorInterface
use Symfony\Contracts\Translation\TranslatorInterface;
$provider = Injector::inst()->get(TranslatorInterface::class);
$provider->setFallbackLocales(['nl']);
GuySartorelli commented 1 year ago

I won't be recommending or making any code change as a result of this issue - but this does seem like it needs to be clearly and prominently documented on the i18n documentation page, so I'm going to migrate this issue to the docs repo.

RVXD commented 1 year ago

Hi Guy, thanks for looking into this.

RVXD commented 1 year ago

I tried all your suggestions, but none of these work. Maybe I'm doing something wrong. Did you test the code you provided?

GuySartorelli commented 1 year ago

I did test it, yes. It was on a fresh installation of installer with no additional modules - though now that I am trying to recall, I can't be certain whether I was using cms 4 of 5.... Will confirm that tomorrow.

RVXD commented 1 year ago

This would work on any 4.x site:

use SilverStripe\Core\Injector\Injector;
use SilverStripe\i18n\Messages\MessageProvider;
$provider = Injector::inst()->create(MessageProvider::class);
$provider->getTranslator()->setFallbackLocales([]);

I think if in Silverstripe you have defined a default value, you would not want it to fallback to english but to that default. So you could argue that the FallbackLocales should always be set to empty array / null to make the default value work.

Note: only changing the TranslatorInterface without instantiating the MessageProvider didn't work.

GuySartorelli commented 1 year ago

Aha! Okay. So the examples I had above are for CMS 5. There's a subtle change in the namespace for TranslatorInterface between CMS 4 and CMS 5.

I've updated the comment above to indicate the correct FQCN to use in CMS 4.

I think if in Silverstripe you have defined a default value, you would not want it to fallback to english but to that default. So you could argue that the FallbackLocales should always be set to empty array / null to make the default value work.

What specific configuration are you referring to when you say "default value"?

RVXD commented 1 year ago

Hi Guy, thanks for the input. The code you supplied for SS4 did not work for me, also the updated version.

Concerning the Default Value:

Template:

<%t MyClass.MyKeyword "My Default Value" %>

en.yml

en:
  MyClass:
    MyKeyword: "English value of keyword"

If my language is set for instance Spanish and MyKeyword is not in the Spanish language file then I think the website should display "My Default Value" and not "English value of keyword".

I think that if it always falls back to English you would have to remove the Default Value from the _t method. If you stick with the _t method with Default Value it should not use English as fallback but the entered Default Value.

GuySartorelli commented 1 year ago

The code you supplied for SS4 did not work for me, also the updated version.

Did you try it in a fresh installation (with your recommended en.yml and nl.yml files)? If not, there may be some customisation, configuration, or module which is interfering with it in a way I don't have any visibility to.

Concerning the Default Value

Oh, you don't mean a global default locale, you mean the default string for any given translation string. Consider a scenario where someone creates a module that has default strings in German, or Russian, or any other language you want to name.

If we didn't declare a default fallback locale for the translation system to use, and there aren't translations for these strings in the current locale a given request is set to use, you'll end up with a mix of English default strings from core and supported modules, whatever-language from this module, and any translated strings your locale provides.

Explicitly declaring a fallback locale reduces the risk of this happening. In your scenario specifically (assuming there is some way to swap locales in runtime e.g. using the fluent module), if you unset the fallback locale you'll have a bunch of default English strings from core and supported (and most community) modules for any request where the locale is set to something where translations aren't available. So you'd still need to explicitly set the fallback locale to the specific locale you want to fall back to.

The default string is only used as a last resort, if there is no translation in the current locale and there is no translation in the fallback locale. This is by design, and is unlikely to change.

RVXD commented 1 year ago

Hi Guy, thanks for explaining and all efforts that went into this!