Angular 6 and i18n in typescript

后端 未结 4 1011
半阙折子戏
半阙折子戏 2021-01-05 15:51

I saw that angular6 implements i18n for its components and that by using i18n you can internationalize your html but can you do the same with typescript? I have two specific

相关标签:
4条回答
  • 2021-01-05 15:53

    You can Extend "ng serve | build" proccess so "AOT compialtion" for i18n translation in .ts is done

    • Main idea is to use jour generated .xlf files(and ids like @@translationId) in .ts
        import { Component } from '@angular/core';
    
        @Component({
          selector: 'app-root',
          templateUrl: './app.component.html',
          styleUrls: ['./app.component.scss']
        })
        export class AppComponent {
          title = 'i18n-ts-demo-ng';
          title2 = '@@my.test.header';
        }
    
    • and translate them in build proces

      1. add "ng add ngx-build-plus"
      2. create plugin
    //create file i18n-plugin.ts in root
    import { I18NTransformer } from './i18n';
    import { AngularCompilerPlugin } from '@ngtools/webpack';
    
    function findAngularCompilerPlugin(webpackCfg): AngularCompilerPlugin | null {
      return webpackCfg.plugins.find(plugin =>  plugin instanceof AngularCompilerPlugin);
    }
    
    // The AngularCompilerPlugin has nog public API to add transformations, user private API _transformers instead.
    function addTransformerToAngularCompilerPlugin(acp, transformer): void {
      acp._transformers = [transformer, ...acp._transformers];
    }
    
    export default {
      pre() {
        // This hook is not used in our example
      },
    
      // This hook is used to manipulate the webpack configuration
      config(cfg) {
        // Find the AngularCompilerPlugin in the webpack configuration
        const angularCompilerPlugin = findAngularCompilerPlugin(cfg);
    
        if (!angularCompilerPlugin) {
          console.error('Could not inject the typescript transformer: Webpack AngularCompilerPlugin not found');
          return;
        }
    
        addTransformerToAngularCompilerPlugin(angularCompilerPlugin, I18NTransformer);
        return cfg;
      },
    
      post() {
        // This hook is not used in our example
      }
    };
    
    //create file i18n.ts in root
    import * as ts from 'typescript';
    
    // TODO move to config
    const RequireAlli18NKeys = false; // if true onda all 18n keys must be found othervse error is thrown;
    
    // Read translations
    import { Xliff, Node } from '@angular/compiler';
    const fs = require('fs');
    const path = require('path');
    
    let localeId: string; // hr || en ...
    
    let i18nLocale = 0; // 0 - parameter not found | 1 - parameter is fount so next is locale string (hr, ...)
    
    // parse parameters
    process.argv.forEach(pParam => {
    
      console.log('param:' + pParam);
      // get Locale is using: ng serve ...
      if (pParam.startsWith('--configuration=')) {
        localeId = pParam.replace('--configuration=', '');
        console.log('Locale:' + localeId);
      }
    
      // Has to be before code down
      if (i18nLocale === 1) {
        i18nLocale = 2;
        localeId = pParam;
        console.log('Locale:' + localeId);
      }
    
      // Get locale if using: ng build --prod --i18n-locale en ...
      if (pParam.startsWith('--i18n-locale')) {
        i18nLocale = 1;
        localeId = pParam.replace('--config--i18n-locale ', '')
      }
    });
    
    // Load translation
    // tslint:disable-next-line:max-line-length
    if (localeId === undefined) { throw new Error(`No language specified.\nUsage: ng serve --configuration=hr --aot --plugin ~dist/out-tsc/i18n-plugin.js`); }
    const content = fs.readFileSync(`src/translate/messages.${localeId}.xlf`, 'utf8');
    const xliff = new Xliff().load(content, '');
    
    export const I18NTransformer = <T extends ts.Node>(context: ts.TransformationContext) => {
      return (rootNode: ts.SourceFile) => {
        function visit(node: ts.Node): ts.Node {
          if (
            rootNode.fileName.includes('node_modules')
            || !rootNode.fileName.includes('.ts')
            // || ts.isToken(node)
          ) {
            return ts.visitEachChild(node, visit, context);
          }
    
          if (ts.isStringLiteral(node)) {
            // teplace @@ with translation
            if (node.text.includes('@@')) {
              // take key for translatioc
              const tSourceKey = node.text;
              const tI18NKey = node.text.replace('@@', '');
              // find key
              const tTranslation: any = xliff.i18nNodesByMsgId[tI18NKey];
              if (tTranslation) {
                // let t1 = tTranslation[0];
    
                // let tLocaleStr = t1.toString(); //tTranslation[0].value;
                const tLocaleStr = tTranslation[0].value;
                console.log(ConsoleColor.BgCyan, 'i18n key: ', ConsoleColor.Reset, tI18NKey + '=> translation   : ' + tLocaleStr);
                const tNew2 = node.text.replace(tSourceKey, tLocaleStr);
                return ts.createStringLiteral(tNew2);
              }
              const tMessage = 'ERROR! No translation for key: ' + tI18NKey + ', source:' + rootNode.fileName;
              console.log(ConsoleColor.BgRed, tMessage, ConsoleColor.Reset);
              if (RequireAlli18NKeys) {
                throw new Error(tMessage);
              }
            }
          }
    
          return ts.visitEachChild(node, visit, context);
        }
        return ts.visitNode(rootNode, visit);
      };
    };
    
    class ConsoleColor {
      static Reset = '\x1b[0m';
      static Bright = '\x1b[1m';
      static Dim = '\x1b[2m';
      static Underscore = '\x1b[4m';
      static Blink = '\x1b[5m';
      static Reverse = '\x1b[7m';
      static Hidden = '\x1b[8m';
    
      static FgBlack = '\x1b[30m';
      static FgRed = '\x1b[31m';
      static FgGreen = '\x1b[32m';
      static FgYellow = '\x1b[33m';
      static FgBlue = '\x1b[34m';
      static FgMagenta = '\x1b[35m';
      static FgCyan = '\x1b[36m';
      static FgWhite = '\x1b[37m';
    
      static BgBlack = '\x1b[40m';
      static BgRed = '\x1b[41m';
      static BgGreen = '\x1b[42m';
      static BgYellow = '\x1b[43m';
      static BgBlue = '\x1b[44m';
      static BgMagenta = '\x1b[45m';
      static BgCyan = '\x1b[46m';
      static BgWhite = '\x1b[47m';
    }
    
    • in terminal start: tsc --skipLibCheck --module umd -w

    • ng serve --configuration=hr --aot --plugin ~dist/out-tsc/i18n-plugin.js

    complete example is at https://github.com/Emanuel3003/i18n-ts-demo-ng

    Regards

    0 讨论(0)
  • 2021-01-05 16:08

    This is not possible through the library's API until now (@angular/language-service v7.2).

    Below is my workaround (thank fredrikredflag for his good post on GitHub and thank @BrunoBruzzano for the link):


    src/app/i18n.service.ts:

    import {Injectable} from "@angular/core";
    import {Xliff2} from '@angular/compiler';
    // You can also import {Xliff} or {Xtb} instead of {Xliff2}, depending on your project configurations
    
    declare const require;
    const content = require('raw-loader!../i18n/messages.fa.xlf');
    
    @Injectable({
        providedIn: 'root'
    })
    export class I18nService {
        private readonly xliff: any = new Xliff2().load(content, '');
    
        get(key: string): string {
            return this.xliff.i18nNodesByMsgId[key][0].value;
        }
    }
    

    i18n pseudo-component (JUST FOR AUTO-GENERATING TRANSLATIONS in messages.xlf file):

    1. src/app/i18n/i18n.component.ts (Isn't important. Just needed to exists.):

      import {Component} from '@angular/core';
      @Component({templateUrl: './i18n.component.html'})
      export class I18nComponent {}
      
    2. src/app/i18n/i18n.component.html (don't forget using an id!)

      <p i18n="@@newVersionAlert">New version available. Load New Version?</p>
      

    Don't forget declaring I18nComponent in your @NgModule.


    Usage (after running ng xi18n ... and translating):

    In your component:

    ...
    import {I18nService} from './i18n.service';
    
    ...
        constructor(private i18nService: I18nService, ...) { ... }
    
        sampleUsage() {
            confirm(this.t('newVersionAlert'));
        }
    
        /**
         * translate
         */
        private t(i18nId: string) {
            return this.i18nService.get(i18nId);
        }
    ...
    

    Utility script to translate i18n.service.ts before build:

    (This requirement: require('raw-loader!../i18n/messages.fa.xlf') needs to be translated to match wanted locale.)

    PreBuild/prebuild.ts:

    import {Xliff2} from "@angular/compiler";  
    // You can also import {Xliff} or {Xtb} from "@angular/compiler" depending of your case.
    
    const fs = require('fs');  
    const path = require('path');  
    
    const localeId = process.argv[2];  
    
    if (localeId === undefined) throw new Error(`No language specified.\nUsage: node ${path.basename(__filename)} <locale-id${'>'}`);  
    
    const content = fs.readFileSync(`src/i18n/messages.${localeId}.xlf`, 'utf8');  
    const xliff = new Xliff2().load(content, '');
    
    const i18nServiceFilePath = './src/app/i18n.service.ts'; 
    
    fs.writeFileSync(i18nServiceFilePath,  
      fs.readFileSync(i18nServiceFilePath, 'utf8')  
        .replace(/(raw-loader!\.\.\/i18n\/messages\.)\w{2}(\.xlf)/, `$1${xliff.locale}$2`)  
    );
    

    PreBuild/tsconfig.json:

    {
        "compilerOptions": {
            "outDir": "./build",
            "lib": [
                "es2018",
                "dom"
            ],
            "module": "commonjs",
            "moduleResolution": "node",
            "target": "es6",
            "typeRoots": [
                "../node_modules/@types"
            ]
        },
        "files": [
            "prebuild.ts"
        ]
    }
    

    package.json:

    ...
    "scripts": {
        "compile-pre-build": "tsc -p PreBuild/tsconfig.json --pretty",
        "pre-build": "node PreBuild/build/prebuild.js",
        ...
    ...
    

    Usage:

    (After one-time npm run compile-pre-build:)

    npm run pre-build -- fa
    

    or

    npm run pre-build -- en
    

    This will edit i18n.service.ts.

    0 讨论(0)
  • 2021-01-05 16:08

    You can use the Transloco library to do this: https://ngneat.github.io/transloco/.

    And then get the translations in the Typescript file like so:

    this.translocoService.translate('hello');
    
    0 讨论(0)
  • 2021-01-05 16:15

    In Angular 9 you can use global $localize function like this:

    $localize`String to translate`
    

    Be aware:

    1. In Angular 9 CLI does not extract these messages with the xi18n, you have to do it manually
    2. Run ng add @angular/localize if you have not gone through $localize migration
    0 讨论(0)
提交回复
热议问题