简体   繁体   English

打字稿中的 Angular 6 和 i18n

[英]Angular 6 and i18n in typescript

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?我看到 angular6 为它的组件实现了 i18n 并且通过使用 i18n 你可以国际化你的 html 但是你可以用 typescript 做同样的事情吗? I have two specific areas我有两个特定的领域

One in a zingChart: - Be able to i18n Example zingChart 中的一个: - 能够 i18n 示例

exampleData = {
 valueBox: {
                text: '<span style="font-size: 32px">%pie-total-value</span> <br/> Example',
                placement: 'center',
                fontWeight: 'normal'
            },
     }

Thank you very much for your time and answers.非常感谢您的时间和答案。

This is not possible through the library's API until now ( @angular/language-service v7.2 ).直到现在( @angular/language-service v7.2 ),这是不可能通过库的 API 实现的。

Below is my workaround (thank fredrikredflag for his good post on GitHub and thank @BrunoBruzzano for the link ):以下是我的解决方法(感谢 fredrikredflag 在 GitHub 上发表的好文章感谢@BrunoBruzzano 提供链接):


src/app/i18n.service.ts : 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) : i18n 伪组件(仅用于在messages.xlf文件中自动生成翻译)

  1. src/app/i18n/i18n.component.ts (Isn't important. Just needed to exists.): src/app/i18n/i18n.component.ts (不重要。只需要存在。):

     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! ) src/app/i18n/i18n.component.html不要忘记使用 id!

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

Don't forget declaring I18nComponent in your @NgModule .不要忘记在I18nComponent中声明@NgModule


Usage (after running ng xi18n ... and translating):用法(运行ng xi18n ...并翻译后):

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 :在构建之前翻译i18n.service.ts实用脚本

(This requirement: require('raw-loader!../i18n/messages.fa.xlf') needs to be translated to match wanted locale. ) (此要求: require('raw-loader!../i18n/messages.fa.xlf')需要翻译以匹配想要的语言环境。

PreBuild/prebuild.ts : 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 : PreBuild/tsconfig.json

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

package.json : 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 compile-pre-build :)

npm run pre-build -- fa

or或者

npm run pre-build -- en

This will edit i18n.service.ts .这将编辑i18n.service.ts

In Angular 9 you can use global $localize function like this:在 Angular 9 中,您可以像这样使用全局 $localize 函数:

$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在 Angular 9 CLI 中不使用 xi18n 提取这些消息,您必须手动执行
  2. Run ng add @angular/localize if you have not gone through $localize migration如果您还没有经历过$localize 迁移,请运行ng add @angular/localize

You can use the Transloco library to do this: https://ngneat.github.io/transloco/ .您可以使用 Transloco 库来执行此操作: https : //ngneat.github.io/transloco/

And then get the translations in the Typescript file like so:然后在 Typescript 文件中获取翻译,如下所示:

this.translocoService.translate('hello');

You can Extend "ng serve | build" proccess so "AOT compialtion" for i18n translation in .ts is done您可以扩展“ng serve | build”过程,以便完成 .ts 中 i18n 翻译的“AOT 编译”

  • Main idea is to use jour generated .xlf files(and ids like @@translationId) in .ts主要思想是在 .ts 中使用 jour 生成的 .xlf 文件(和像 @@translationId 这样的 id)
    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"添加“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在终端启动: tsc --skipLibCheck --module umd -w

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

complete example is at https://github.com/Emanuel3003/i18n-ts-demo-ng完整示例在https://github.com/Emanuel3003/i18n-ts-demo-ng

Regards问候

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM