Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
RVXD opened this issue Jun 28, 2023 · 10 comments
Open

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

RVXD opened this issue Jun 28, 2023 · 10 comments

Comments

@RVXD
Copy link
Contributor

RVXD commented Jun 28, 2023

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
Copy link
Member

GuySartorelli commented Jun 29, 2023

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
Copy link
Member

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.

@GuySartorelli GuySartorelli changed the title i18n translation does not show default value but English translation Document how to unset and change the fallback locale for i18n Jun 29, 2023
@GuySartorelli GuySartorelli transferred this issue from silverstripe/silverstripe-framework Jun 29, 2023
@RVXD
Copy link
Contributor Author

RVXD commented Jun 29, 2023

Hi Guy, thanks for looking into this.

@RVXD
Copy link
Contributor Author

RVXD commented Jun 29, 2023

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

@GuySartorelli
Copy link
Member

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
Copy link
Contributor Author

RVXD commented Jun 29, 2023

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
Copy link
Member

GuySartorelli commented Jun 30, 2023

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
Copy link
Contributor Author

RVXD commented Jun 30, 2023

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
Copy link
Member

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
Copy link
Contributor Author

RVXD commented Jul 3, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants