i18n-polyfill icon indicating copy to clipboard operation
i18n-polyfill copied to clipboard

Fail to i18n strings with parameters containing {}

Open Chocobozzz opened this issue 7 years ago • 5 comments

Hi,

The polyfill fails to render strings with parameters containing {}. Origin issue: https://github.com/Chocobozzz/PeerTube/issues/756

How to reproduce:

Add

  it("Should support parameters with special characters", () => {
    const i18nService = getService();
    expect(i18nService("This is a test {{ok}} !", {ok: "{hello}"})).toBe("Ceci est un test {hello} !");
  });

to i18n-polyfill.service.spec.ts test file.

Stacktrace:

  ● Polyfill › Should support parameters with special characters

    Unexpected character "EOF" (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.) ("Ceci est un test {hello} ![ERROR ->]"): 0:26
    Invalid ICU message. Missing '}'. ("Ceci est un test {hello} ![ERROR ->]"): 0:26

      63 |     private i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
      64 |     public digest: (m: i18n.Message) => string,
    > 65 |     interpolationConfig: InterpolationConfig,
      66 |     missingTranslationStrategy: MissingTranslationStrategy,
      67 |     public mapperFactory?: (m: i18n.Message) => PlaceholderMapper,
      68 |     console?: Console
      
      at TranslationBundle.get (lib/src/parser/html.ts:65:19)
      at Visitor.translateMessage (lib/src/parser/html.ts:358:45)
      at Visitor.visitElement (lib/src/parser/html.ts:285:45)
      at Element.visit (lib/src/ast/ast.ts:68:24)
      at Visitor.merge (lib/src/parser/html.ts:228:37)
      at HtmlParser.mergeTranslations (lib/src/parser/html.ts:33:24)
      at I18n (lib/src/i18n-polyfill.ts:65:44)
      at it (test/i18n-polyfill.service.spec.ts:88:16)
      at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:392:26)
      at ProxyZoneSpec.Object.<anonymous>.ProxyZoneSpec.onInvoke (node_modules/zone.js/dist/proxy.js:79:39)
      at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:391:32)
      at Zone.Object.<anonymous>.Zone.run (node_modules/zone.js/dist/zone.js:142:43)
      at Object.testBody.length (node_modules/jest-zone-patch/index.js:50:27

Is it up to us to escape parameters values?

Thanks

Chocobozzz avatar Jul 09 '18 13:07 Chocobozzz

Tried to escape curly braces. This didn't work for me. Escaping gives escaped symbol in the translation file. Removing escaping in the translation file gives an error. Just refused from using curly braces at all

skirilo avatar Oct 10 '18 10:10 skirilo

Can anyone tell me how can I use i18n outside the class like enums or const arrays something like .model.ts files?

NagaSainath avatar Jul 18 '19 15:07 NagaSainath

In order to use i18n outside of classes you need to initialize this statically before bootstrapping. This may look like including some translation manager with a static method which returns an instance of this translation service. Name it as i18n at the beginning of your .model.ts file

skirilo avatar Jul 22 '19 07:07 skirilo

@skirilo Do you have a example for me if you don't mind

NagaSainath avatar Jul 22 '19 13:07 NagaSainath

@NagaSainath I think this is a wrong thread for this discussion. But I'll give my answer here. Please move it to the correct thread if needed.

  1. You need a "translation service". Suppose you add it to a module with name "yourmodulewiththetranslationservice". And let's name it translation.service.ts:
import {MissingTranslationStrategy} from "@angular/core";
import {I18n, I18nDef} from "@ngx-translate/i18n-polyfill";
import {InjectionToken} from '@angular/core';

export const USE_TRANSLATION_SERVICE = new InjectionToken<boolean>('useTranslationService');
let translationService: I18n;

export const i18nServiceFactory = (useService: boolean = true) => {
  if(!translationService) {
      if(useService) {
          let baseLocation = "base location of your app";
          let loadingTranslationFailed = false;
          let translations = "";
          try {
              let request = new XMLHttpRequest();
              request.open('GET', baseLocation + '/locale/translations.xlf', false);  // `false` makes the request synchronous
              request.send(null);
              if (request.status === 200) {
                  translations = request.responseText;
              } else {
                  console.log("Translation file could not be loaded.");
              }
          } catch (e) {
              loadingTranslationFailed = true;
              console.log("Translation file could not be loaded.");
          }

          if(!loadingTranslationFailed) {
              translationService = new I18n("xlf", translations, 'en', MissingTranslationStrategy.Ignore);
              if(translations.length > 0)
                console.log('Translation service was initialized successfully');
              else
                  console.log('Default translation values will be used');
              return translationService;
          }
      }

      console.error('Translation service was not initialized properly. Dummy translation service will be used');
      translationService = (value: string | I18nDef, params: { [key: string]: any; }) => {
          if(typeof value == 'string')
            return value;
          else if(typeof value == 'object') //instanceof I18nDef
              return value.value;
          else
              'Unknown value type';
      }
  }
  return translationService;
};
  1. In your app.module.ts: In imports add
import { I18n } from '@ngx-translate/i18n-polyfill';
import { i18nServiceFactory, USE_TRANSLATION_SERVICE} from "@yourmodulewiththetranslationservice";

Add this code to the corresponding place below:

@NgModule({
// .... some code
providers: [
// ...
  {provide: USE_TRANSLATION_SERVICE, useValue:true},
  {provide: I18n, useFactory: i18nServiceFactory, deps:[USE_TRANSLATION_SERVICE]},
// ...
],
  1. In the file where you need to initialize some static content you need to add the following:
import {I18n} from "@ngx-translate/i18n-polyfill";
import {i18nServiceFactory} from "@yourmodulewiththetranslationservice";

const i18n: I18n = i18nServiceFactory();

Then use it like this:

export const SomeConstants = {
  STRCONSTANTNAME: i18n({value: 'Some text to translate', id: 'Constants_ConstantUniqueID'}),
}

And quite a lot of time passed since I used it. As far as I remember I altered the extraction tool as well to process nodes with 'i18n' name to have automatic extraction

skirilo avatar Jul 23 '19 10:07 skirilo