0%

关于ORM

参考阮一峰《ORM 实例教程》:
面向对象编程(我们的后台)把所以实体看成对象(即Object),关系型数据库则是采用实体之间的关系(即Relation)连接数据,打通后台对象和关系数据库之间的关系而做的映射(Mapping)就是ORM。或者说ORM是通过对象实例语法,完成关系型数据库操作的技术。

举个栗子

1
2
3
SELECT id, first_name, last_name, phone, birth_date, sex
FROM persons
WHERE id = 10

编程调用SQL的写法大致是
1
2
res = db.execSql(sql);
name = res[0]["FIRST_NAME"];

ORM要实现的写法是
1
2
p = Person.get(10);
name = p.first_name;

优点:

  • 数据模型都在一个地方定义,更容易更新和维护,也利于重用代码。
  • ORM 有现成的工具,很多功能都可以自动完成,比如数据消毒、预处理、事务等等。
  • 它迫使你使用 MVC 架构,ORM 就是天然的 Model,最终使代码更清晰。
  • 基于 ORM 的业务代码比较简单,代码量少,语义性好,容易理解。
  • 你不必编写性能不佳的 SQL。

缺点

  • ORM 库不是轻量级工具,往往需要花很多精力学习和设置。
  • 对于复杂的查询,ORM 要么是无法表达,要么是性能不如原生的 SQL。
  • ORM 抽象掉了数据库层,开发者无法了解底层的数据库操作,也无法定制一些特殊的 SQL。

(删除线部分QQs不是特别赞同)

同属ORM技术的比如 Hibernate, MyBatis

关于ODBC

开放式数据库连接(Open Database Connection),连接数据库进行查询的规范(specification)

entity framwork,dbcontext, datarepository

Include, ThenInclude:

1
2
3
result = await _context.employees.Where(employee => employee.level == highlvl)
.Include(employee => employee.department)
.ThenInclude(department => department.bills).ToListAsync();

多对多关系会造成循环引用(circular reference),默认情况下序列化类会报异常,参考Add mechanism to handle circular references when serializing

Dapper.net

据说vsftp是very safe FTP, vsftp服务以ssl保护数据传输,使用22端口而不是21端口。

1
sudo apt-get install vsftpd

参数配置:/etc/vsftpd.conf,用cp命令备份下先
参数定义—>here

修改配置后重启

1
service vsftpd restart

FileZilla

FastDFS是国人大神开发的用于小文件(<500Mb)存储的分布式文件管理系统

Github: happyfish100/fastdfs

戳—>详细配置步骤,超级详细,傻瓜式,环境是Centos。对于ubuntu,安装编译环境的方式会有不同

centos编译环境

1
2
yum install git gcc gcc-c++ make automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl-devel wget vi -y

ubuntu编译环境

1
apt install git gcc g++ make automake autoconf libtool libpcre3 libpcre3-dev zlib1g zlib1g-dev  libssl-dev wget vi

tracker和storage

fdfs
如配置步骤所述,fdfs安装好后有tracker配置文件和storage配置文件,前者配置tracker用于上传下载的调度,后者配置storage作为文件存储。

tracher监听storage的状态同步消息,使当client上传或下载时,提供可用的storage路径

storage可以配置为group,相同group的文件会相互拷贝(这个是需要一定时间的,在集群方案中需要考虑)

上传文件

上传
为使业务应用服务器实现上传fDFS,应实现client功能,即

  • 请求tracker 获取可用storage的 ip port等
  • 调用相应的storage接口上传文件,接受返回的file_id信息

下载文件

下载
如图,client实现下载,需

  • 请求tracker 获拉取下载的storage的 ip port等
  • 调用相应的storage接口下载文件

变量和常量

a. 用var关键字来定义变量,使用$来引用变量。

b. 寄存器变量 $0~$9,$R0~$R9

c. 系统预置变量

  • $INSTDIR
    用户定义的解压路径。
  • $PROGRAMFILES
    程序文件目录(通常为 C:\Program Files 但是运行时会检测)。
  • $COMMONFILES
    公用文件目录。这是应用程序共享组件的目录(通常为 C:\Program Files\Common Files 但是运行时会检测)。
  • $DESKTOP
    Windows 桌面目录(通常为 C:\windows\desktop 但是运行时会检测)。该常量的内容(所有用户或当前用户)取决于 SetShellVarContext 设置。默认为当前用户。
  • $EXEDIR
    安装程序运行时的位置。(从技术上来说你可以修改改变量,但并不是一个好方法)。
  • ${NSISDIR}
    包含 NSIS 安装目录的一个标记。在编译时会检测到。常用于在你想调用一个在 NSIS 目录下的资源时,例如:图标、界面……
  • $WINDIR
    Windows 目录(通常为 C:\windows 或 C:\winnt 但在运行时会检测)
  • $SYSDIR
    Windows 系统目录(通常为 C:\windows\system 或 C:\winnt\system32 但在运行时会检测)
  • $TEMP
    系统临时目录(通常为 C:\windows\temp 但在运行时会检测)
  • $STARTMENU
    开始菜单目录(常用于添加一个开始菜单项,使用 CreateShortCut)。该常量的内容(所有用户或当前用户)取决于 SetShellVarContext 设置。默认为当前用户。
  • $SMPROGRAMS
    开始菜单程序目录(当你想定位 + $STARTMENU\程序 时可以使用它)。该常量的内容(所有用户或当前用户)取决于 SetShellVarContext 设置。默认为当前用户。
  • $SMSTARTUP
    开始菜单程序/启动 目录。该常量的内容(所有用户或当前用户)取决于 SetShellVarContext 设置。默认为当前用户。
  • $QUICKLAUNCH
    在 IE4 活动桌面及以上的快速启动目录。如果快速启动不可用,仅仅返回和 + $TEMP 一样。
  • $DOCUMENTS
    文档目录。一个当前用户典型的路径形如 C:\Documents and Settings\Foo\My Documents。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext 设置。默认为当前用户。
    该常量在 Windows 95 且 Internet Explorer 4 没有安装时无效。
  • $SENDTO
    该目录包含了“发送到”菜单快捷项。
  • $RECENT
    该目录包含了指向用户最近文档的快捷方式。
  • $FAVORITES
    该目录包含了指向用户网络收藏夹、文档等的快捷方式。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext 设置。默认为当前用户。
    该常量在 Windows 95 且 Internet Explorer 4 没有安装时无效。
  • $MUSIC
    用户的音乐文件目录。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext 设置。默认为当前用户。
    该常量仅在 Windows XP、ME 及以上才有效。
  • $PICTURES
    用户的图片目录。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext 设置。默认为当前用户。
    该常量仅在 Windows 2000、XP、ME 及以上才有效。
  • $VIDEOS
    用户的视频文件目录。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext 设置。默认为当前用户。
    该常量仅在 Windows XP、ME 及以上才有效。
  • $NETHOOD
    该目录包含了可能存在于我的网络位置、网上邻居文件夹的链接对象。
    该常量在 Windows 95 且 Internet Explorer 4 和活动桌面没有安装时无效。
  • $FONTS
    系统字体目录。
  • $TEMPLATES
    文档模板目录。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext 设置。默认为当前用户。
  • $APPDATA
    应用程序数据目录。当前用户路径的检测需要 Internet Explorer 4 及以上。所有用户路径的检测需要 Internet Explorer 5 及以上。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext 设置。默认为当前用户。
    该常量在 Windows 95 且 Internet Explorer 4 和活动桌面没有安装时无效。
  • $PRINTHOOD
    该目录包含了可能存在于打印机文件夹的链接对象。
    该常量在 Windows 95 和 Windows 98 上无效。
  • $INTERNET_CACHE
    Internet Explorer 的临时文件目录。
    该常量在 Windows 95 和 Windows NT 且 Internet Explorer 4 和活动桌面没有安装时无效。
  • $COOKIES
    Internet Explorer 的 Cookies 目录。
    该常量在 Windows 95 和 Windows NT 且 Internet Explorer 4 和活动桌面没有安装时无效。
  • $HISTORY
    Internet Explorer 的历史记录目录。
    该常量在 Windows 95 和 Windows NT 且 Internet Explorer 4 和活动桌面没有安装时无效。
  • $PROFILE
    用户的个人配置目录。一个典型的路径如 C:\Documents and Settings\Foo。
    该常量在 Windows 2000 及以上有效。
  • $ADMINTOOLS
    一个保存管理工具的目录。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext 设置。默认为当前用户。
    该常量在 Windows 2000、ME 及以上有效。
  • $RESOURCES
    该资源目录保存了主题和其他 Windows 资源(通常为 C:\Windows\Resources 但在运行时会检测)。
    该常量在 Windows XP 及以上有效。
  • $RESOURCES_LOCALIZED
    该本地的资源目录保存了主题和其他 Windows 资源(通常为 C:\Windows\Resources\1033 但在运行时会检测)。
    该常量在 Windows XP 及以上有效。
  • $CDBURN_AREA
    一个在烧录 CD 时储存文件的目录。.
    该常量在 Windows XP 及以上有效。
  • $HWNDPARENT
    父窗口的十进制 HWND。
  • $PLUGINSDIR
    该路径是一个临时目录,当第一次使用一个插件或一个调用 InitPluginsDir 时被创建。该文件夹当解压包退出时会被自动删除。这个文件夹的用意是用来保存给 InstallOptions 使用的 INI 文件、启动画面位图或其他插件运行需要的文件。

函数

NSIS函数本身没有输出输出,但是可以访问变量和堆栈

使用Pop Push实现输入输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Function LCIDtoTAG 
${case} "1036"
Push "fr_FR"
${break}
${case} "1049"
Push "ru_RU"
${break}
${case} "1041"
Push "ja_JP"
${break}
${case} "2052"
Push "zh_CN"
${break}
${default}
Push "en_US"
${break}
${endswitch}
FunctionEnd
Section
Call LCIDtoTAG
Pop $0
SectionEnd

跳转

1
2
3
4
StrCmp $1 "" +1 +2
DetailPrint "parameter is empty"

DetailPrint "parameter is not empty"

+n是从该语句向下偏移的“指针”,空行过滤掉不计入偏移量

—> NSIS收录插件 https://nsis.sourceforge.io/Category:Plugins

—> 插件本体是托管dll文件,区分unicode编码和ansi编码

—> NSIS默认检索在NSIS代码目录下的Plugins文件夹,作为插件路径,或可使用标识符!addplugindir 指定其他目录

一个栗子

1
2
3
4
5
6
7
8
9
!ifndef TARGETDIR
!ifdef NSIS_UNICODE
!define TARGETDIR "..\binU"
!else
!define TARGETDIR "..\bin"
!endif
!endif

!addplugindir "${TARGETDIR}"

曾将!ifdef误解为if not define,NSIS用!xxx做关键字真的是坑。

NGX-Translate is an internationalization library for Angular. NGX-Translate is also extremely modular. It is written in a way that makes it really easy to replace any part with a custom implementation in case the existing one doesn’t fit your needs.

Why ngx-translate exists if we already have built-in Angular i18n

关于载入翻译文件

集成步骤

1
npm install @ngx-translate/core @ngx-translate/http-loader --save

在模块中引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {TranslateModule} from '@ngx-translate/core';

export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: createTranslateLoader,
deps: [HttpClient]
},
defaultLanguage: 'en'
})
],
bootstrap: [AppComponent]
})
export class AppModule { }

组件注入及初始化
1
2
3
4
5
6
7
8
export class AppComponent {
constructor(translate: TranslateService) {
// this language will be used as a fallback when a translation isn't found in the current language
translate.setDefaultLang('en');
// the lang to use, if the lang isn't available, it will use the current loader to get them
translate.use('en');
}
}

在模板中使用管道标记待翻译标记
1
2
3
4
5
<p>{{"sayHi" | translate}}</p>

<p>{{'sayHiWithParameter' | translate:userObj}}</p>

<p>{{ 'ROLES.' + role | uppercase | translate }}</p>

翻译文件,对于HTTP Loader是以Locale_ID命名的Json
1
2
3
4
5
6
src
├───assets
│ ├───i18n
│ │ en.json
│ │ fr.json
│ │ zh.json

翻译格式
1
2
3
4
5
6
7
8
{
"introductionHeader":"你好",
"ROLE":{
"ADMIN": "管理员",
"USER": "用户"
},
"sayHiWithParameter":"你好,{{pride}}的{{lastname}}}先生"
}

在ts代码中引用翻译
1
2
3
4
translate.get('HELLO', {value: 'world'}).subscribe((res: string) => {
console.log(res);
//=> 'hello world'
});

使用 PO Loader

官方提供给的 @biesbjerg/ngx-translate-po-http-loader,很遗憾,并不好使,或许是因为新版本的gettext-parser将msgctxt(信息上下文)作为一级父节点,而ngx-translate-po-http-loader一直没有更新使支持msgctxt

部分zh_CN.po文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
msgid ""
msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Language: zh_CN\n"
"X-Qt-Contexts: true\n"

#: ../scanflow/res/qml/Dialog/AboutDialog.qml:25
msgctxt "AboutDialog|"
msgid "About"
msgstr "关于"

#: ../scanflow/res/qml/Dialog/AboutDialog.qml:52
msgctxt "AboutDialog|"
msgid "Product Version"
msgstr "产品版本"

通过上文“翻译格式”可知,ngx-translate允许多级属性mapping,如
1
<h1>{{ "AboutDialog|.About" |translate}}</h1>

另外,gettext-parser会将分段的msgid和msgstr进行合并,即
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
msgctxt "ErrorMessage|"
msgid ""
"Application preferences have been damaged. Reinstall the application to "
"solve the problem."
msgstr ""
"Předvolby aplikace byly poškozeny. Problém vyřešíte opětovnou instalací "
"aplikace."
====>
{
...
"ErrorMessage|":{
msgctxt:"ErrorMessage|",
msgid:"Application preferences have been damaged. Reinstall the application to solve the problem.",
msgstr:["Předvolby aplikace byly poškozeny. Problém vyřešíte opětovnou instalací aplikace."]
}
}

原理见@ngx-translate/core/fesm2015/ngx-translate-core.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
getValue(target, key) {
/** @type {?} */
let keys = typeof key === 'string' ? key.split('.') : [key];
key = '';
do {
key += keys.shift();
if (isDefined(target) && isDefined(target[key]) && (typeof target[key] === 'object' || !keys.length)) {
target = target[key];
key = '';
}
else if (!keys.length) {
target = undefined;
}
else {
key += '.';
}
} while (keys.length);
return target;
}

结合ngx-translate解析翻译时的逻辑,在实现PO Loader的getTranslation方法(关于自定义loader的继承和实现,ngx-translate有指引 -> link)时,应将msgctxt,msgid转为父子属性形式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public getTranslation(lang: string): Observable<any> {
return this._http
.get(`${this._prefix}/${lang}${this._suffix}`, { responseType: 'text' })
.pipe(
map((contents: string) => this.parse(contents)));
}

/**
* Parse po file
* @param contents
* @returns {any}
*/
public parse(contents: string): any {
let translations: { [key: string]: object | string } = {};

const po = gettext.po.parse(contents, 'utf-8');
if (!po.translations.hasOwnProperty(this.domain)) {
return translations;
}

Object.keys(po.translations)
.forEach(domain => {
if (domain.length === 0) { return; }
if (po.translations[domain].msgstr) { // there is no msgctxt
translations[domain] = po.translations[domain].msgstr;
} else {
// context
translations[domain] = {};
Object.keys(po.translations[domain]).forEach(key => {
let translation: string | Array<string> = po.translations[domain][key].msgstr.pop();
if (translation instanceof Array && translation.length > 0) {
translation = translation[0];
}
if (key.length > 0 && translation.length > 0) {
translations[domain][key] = translation;
}
});
}
});
return translations;
}

引入自己的 loader module
1
2
3
import { TranslatePoHttpLoader } from './edited-po-loader';
export function createTranslateLoader(http: HttpClient) {
return new TranslatePoHttpLoader(http, 'assets/i18n', '.po');

缺失的翻译 handle missing translations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {MissingTranslationHandler, MissingTranslationHandlerParams} from '@ngx-translate/core';

export class MyMissingTranslationHandler implements MissingTranslationHandler {
handle(params: MissingTranslationHandlerParams) {
return 'some value';
}
}
@NgModule({
imports: [
BrowserModule,
TranslateModule.forRoot({
missingTranslationHandler: {provide: MissingTranslationHandler, useClass: MyMissingTranslationHandler},
useDefaultLang: false
})
],
providers: [

],
bootstrap: [AppComponent]
})
export class AppModule { }

plural and select

这个库没有相应的api与i18n的 plural / select 模式相对应,对于这些场景需要使用ngIf或ngSwitch指令实现

ng build, ng serve是JIT, ng build —aot, ng build —prod, ng serve —aot是AOT 从Angular 9开始,默认情况下,对于提前编译器,编译选项设置为true。

JIT(Just in Time)由浏览器将源码编译成js执行,QQs:浏览器居然可以编译代码!
AOT(Ahead of Time)先编译成可执行的js,再交给浏览器

The Angular ahead-of-time (AOT) compiler converts Angular HTML and TypeScript code into efficient JavaScript code during the build phase, before the browser downloads and runs that code. This is the best compilation mode for production environments, with decreased load time and increased performance compared to just-in-time (JIT) compilation.AOT 编译器在浏览器下载并运行之前,将Angular HTML、 ts代码转为es5代码,是生产环境的最佳实践,相比JIT更能缩短加载时间并提高性能
(aot会根据angular.json 的配置生成到/dist之类的目录) 在angular.json中配置build命令的选项,包括生成目录等

what is aot and jit compiler in angular

Li Mei’s Blog: Angular深入理解编译机制
采取两种编译方式,注意修改angular.json architect.build.option.aot改为false,比较不使用—aot和使用时生成js的内容(见main.js, vendor.js是包含compiler等工具链的源码),可见生成的js包含angular模板语法,只是ts编译成了es5

另,使用source-map-explorer工具分析编译生成的js文件

1
npx source-map-explorer dist/main.js --no-border-checks

Angular Doc: AOT工作原理
YouTube: ng-conf
Angular编译机制(AOT、JIT)
AOT和ngc
ngc是专用于Angular项目的tsc替代者。它内部封装了tsc,还额外增加了用于Angular的选项、输出额外的文件。配置见于tsconfig.json, tsc读取tsconfig配置文件的compilerOptions部分,ngc读取angularCompilerOptions部分。

to be continue

数据层应用程序 (DAC) 是一个逻辑数据库管理实体,用于定义与用户数据库关联的所有 SQL Server 对象,如表、视图和实例对象(包括登录名)。 DAC 是 SQL Server 数据库部署的一个自包含单元,它使数据层开发人员和数据库管理员能够将 SQL Server 对象打包到一个名为“DAC 包”(也称作 DACPAC)的可移植项目中。

Transact-SQL(又称T-SQL),是在Microsoft SQL Server和Sybase SQL Server上的ANSI SQL实现,与Oracle的PL/SQL性质相近(不只是实现ANSI SQL,也为自身数据库系统的特性提供实现支持),当前在Microsoft SQL Server和Sybase Adaptive Server中仍然被使用为核心的查询语言。

下文涉及的函数/方法限T-SQL使用,在MySQL和Oracle中未必兼容

存储过程的查询条件

待改进的一种条件拼接:

1
2
3
SELECT * FROM LocalExport where 
SSO=IIF(@SSO is null, SSO, @SSO)
and SN=IIF(@SN is null, SN, @SN)

T-SQL方法
IIF(expression, return value when ture, return value when false)

待改进是因为存在下述bug:当缺省SN过滤条件(即@SN为null)时,记录中SN列的值为空的行不会查出,即null=null为false

可以这么表达
1
2
3
SELECT * from LocalExport where 
(ISNULL(@SSO, '')='' OR SSO=@SSO)
and (ISNULL(@SN, '')='' OR SN=@SN)

动态SQL语句
1
2
3
4
5
6
7
8
9
10
SET @SQL='select * from LocalExport where 1=1';
IF @SSO is not null
BEGIN
SET @SQL=@SQL+' AND SSO=@SSO'
END
IF @SN is not null
BEGIN
SET @SQL=@SQL+' AND SN=@SN'
END
EXEC sp_executesql @SQL

国际化和本地化

Internationalization is the process of designing and preparing your app to be usable in different languages. Localization is the process of translating your internationalized app into specific languages for particular locales.

国际化是将应用程序设计以及预备,使之支持不同的语言的过程。 本地化是一个把国际化的应用根据区域配置翻译成特定语言的过程。

国际化设计是实现“多语言切换”的前提,angular框架的国际化基础是i18n模块

框架本地化(Localization)

为应用配置“地区”,这个地区成为查找相应的本地化数据的依据。

1
ng add @angular/localize

异常:

Uncaught Error: It looks like your application or one of its dependencies is using i18n.
Angular 9 introduced a global `$localize()` function that needs to be loaded.
Please run `ng add @angular/localize` from the Angular CLI.
(For non-CLI projects, add `import ‘@angular/localize/init’;` to your `polyfills.ts` file.

如上所述,需要添加 import ‘@angular/localize/init’ 到 polyfills.ts

模板翻译

  1. 在组件模板中标记需要翻译的静态文本信息。

    1
    2
    <h1 i18n>Hello QQs</h1>
    <h1 i18n="say hello|translate hello">Hello QQs</h1>

    在组件上添加i18n属性,标记该文本待翻译,另外可附上“<意图>|<描述>”,注意这些文字并不作为翻译条目的标识符,只是增强代码的可读性

    i18n提取工具会为这些标记的单元生成一个随机的id,这个才是标识符,该id可自定义如下

    1
    <h1 i18n="@@introductionHeader">Hello QQs</h1>

    非文本内容标签(待翻译文本是组件属性,而非内容)的标记

    1
    <img [src]="logo" i18n-title title="Angular logo" />

    单复数问题

    1
    2
    3
    4
    5
    <span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}</span>
    <!-- 期望: -->
    <!-- 当minutes=0时,翻译文本“Updated just now” -->
    <!-- 当minutes=1时,翻译文本“Updated one minute ago” -->
    <!-- 其他,翻译文本“Updated xx minutes ago” -->

    plural /ˈplʊərəl/ 复数的 这里是复数翻译模式的关键字

    选择问题

    1
    <span i18n>The author is {gender, select, male {male} female {female} other {other}}</span>
  2. 创建翻译文件:使用 Angular CLI 的 xi18n 命令,把标记过的文本提取到一个符合行业标准的翻译源文件中。
    1
    ng xi18n --i18n-format=xlf --output-path src/locale --out-file translate.xlf
    得到的是一个xml语言的标准格式文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <?xml version="1.0" encoding="UTF-8" ?>
    <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
    <file source-language="en" datatype="plaintext" original="ng2.template">
    <body>
    <trans-unit id="introductionHeader" datatype="html">
    <source>Hello QQs</source>
    <context-group purpose="location">
    <context context-type="sourcefile">src/app/app.component.html</context>
    <context context-type="linenumber">353</context>
    </context-group>
    </trans-unit>
    <trans-unit id="updatetime" datatype="html">
    <source>Updated <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source>
    <context-group purpose="location">
    <context context-type="sourcefile">src/app/app.component.html</context>
    <context context-type="linenumber">354</context>
    </context-group>
    </trans-unit>
    <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html">
    <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source>
    <context-group purpose="location">
    <context context-type="sourcefile">src/app/app.component.html</context>
    <context context-type="linenumber">354</context>
    </context-group>
    </trans-unit>
    </body>
    </file>
    </xliff>
  3. 编辑所生成的翻译文件:把提取出的文本翻译成目标语言。
    复制上面生成的翻译格式文件,填入翻译后的文本

    1
    2
    3
    4
    5
    6
    7
    8
    <trans-unit id="introductionHeader" datatype="html">
    <source>Hello QQs</source>
    <target>你好,爸爸</target>
    </trans-unit>
    <trans-unit id="updatetime" datatype="html">
    <source>Updated <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source>
    <target>{VAR_PLURAL, plural, =0 {方才} =1 {il y 刚片刻} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> 分钟前} }</target>
    </trans-unit>
  4. 将目标语言环境和语言翻译添加到应用程序的配置中。

    a. 配置AOT编译

    警告,此处存在版本差异:

    Angular v9 angular.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    "projects": {
    ...
    "my-project": {
    ...
    "i18n": {
    "sourceLocale": "en-US",
    "locales": {
    "fr": "src/locale/messages.fr.xlf",
    "zh": "src/locale/translate.zh-cn.xlf"
    }
    }
    }
    }

    调用ng build —prod —localize构建 i18n 下定义的所有语言环境。生成若干套静态资源:

    1
    2
    3
    4
    5
    dist
    └───my-project
    ├───en-US
    ├───fr
    └───zh

    配置特定的语言环境,指定Locale_ID

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    "build": {
    "configurations": {
    "fr": {
    "aot": true,
    "outputPath": "dist/my-project-fr/",
    "i18nFile": "src/locale/messages.fr.xlf",
    "i18nFormat": "xlf",
    "i18nLocale": "fr",
    "i18nMissingTranslation": "error" //报告缺失的翻译 提醒级别为error
    }
    ...
    }
    ...
    }

    Angular v8 angular.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    ...
    "architect": {
    "build": {
    "builder": "@angular-devkit/build-angular:browser",
    "options": { ... },
    "configurations": {
    "fr": {
    "aot": true,
    "outputPath": "dist/my-project-fr/",
    "i18nFile": "src/locale/messages.fr.xlf",
    "i18nFormat": "xlf",
    "i18nLocale": "fr",
    "i18nMissingTranslation": "error",
    }
    }
    },
    ...
    "serve": {
    "builder": "@angular-devkit/build-angular:dev-server",
    "options": {
    "browserTarget": "my-project:build"
    },
    "configurations": {
    "production": {
    "browserTarget": "my-project:build:production"
    },
    "fr": {
    "browserTarget": "my-project:build:fr"
    }
    }
    }
    }

    可见 angular.json中,为ng build命令添加了3个用于国际化的参数i18nFile,i18nFormat,i18nLocale。另外指定outputPath用以区分不同语言版本, 这些参数也可以通过cli附加命令的配置项

    1
    ng build --prod --i18n-file src/locale/messages.fr.xlf --i18n-format xlf --i18n-locale fr

    ng serve 命令是通过browsertarget:my-project:build使用ng build 的配置,并不直接支持上述用于国际化的参数,而以下面的方式调用

    1
    ng serve --configuration=fr

    b. 配置JIT编译

    • 载入合适的翻译文件,作为字符串常量
    • 创建相应的translation provider
    • 使用translation provider启动app

    main.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import { enableProdMode, TRANSLATIONS, TRANSLATIONS_FORMAT, MissingTranslationStrategy } from '@angular/core';
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

    import { AppModule } from './app/app.module';
    import { environment } from './environments/environment';

    if (environment.production) {
    enableProdMode();
    }

    // use the require method provided by webpack
    declare const require;
    // we use the webpack raw-loader to return the content as a string
    const translations = require('raw-loader!./locale/translate.fr.xlf').default;

    platformBrowserDynamic().bootstrapModule(AppModule,{
    missingTranslation: MissingTranslationStrategy.Error,
    providers: [
    {provide: TRANSLATIONS, useValue: translations},
    {provide: TRANSLATIONS_FORMAT, useValue: 'xlf'}
    ]
    })
    .catch(err => console.error(err));

    app.module.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @NgModule({
    declarations: [
    AppComponent
    ],
    imports: [
    BrowserModule,
    AppRoutingModule
    ],
    providers: [{ provide: LOCALE_ID, useValue: 'fr' }],
    bootstrap: [AppComponent]
    })
    export class AppModule { }

多语言切换 ngx-translate

关于ngx-translate Ngx-translate文章