present (sb for a job etc)
Let's start with the nouns.
Internationalization is abbreviated as i18n in Chinese.
Globalization is synonymous with Internationalization.
The abbreviation for Localization is l10n, which is Chinese for localization.
i18n vs l10n
One is internationalized and the other is localized, what is the difference between the two and how are they related?
Let's look at a specific example
Above, Apple gives Americans access to theofficial websiteThe content is the price of the iPhone 16 Pro.
The text is in American English (en-US) and the prices are in US dollars (USD).
Okay, let's look at the other two charts
Figure 1 was shown to the Chinese by Appleofficial websiteFigure 2 is for the Japanese.official website。
Chinese people look for Simplified Chinese (zh-Hans-CN) and Chinese Yuan (CNY).
Japanese people look at the Japanese language (ja-JP) and the Japanese Yen (JPY).
All three sites sell the iPhone 16 Pro. The site design and layout are identical.
The only difference is that the website displays the corresponding language and currency depending on the country.
With a website like this one, we can say: Apple's official website supports internationalization and implements localization at the same time.
By supporting internationalization, we mean that the website architecture has the ability to handle different languages, currencies, and time zones. (The design and functionality are the same, but the language, currency and time zone are different.)
By implementing localization, I mean that the website not only has the ability to handle different languages, currencies, and time zones, but it actually does it.
Internationalization refers to a solution/preparation, while localization is the concrete implementation.
Angular i18n
Angular has a built-in i18n scheme. We can use Angular to make websites that support internationalization like Apple does.
This post will step by step teach i18n, but won't explain the principles or stroll through the source code to get started 🚀.
consultation
YouTube – Introduction to Internationalization in Angular
Docs – Angular Internationalization
Angular i18n step by step
one step at a time
Create a new project
ng new i18n --routing=false --ssr=false --skip-tests --style=scss
Install @angular/localizepackage
ng add @angular/localize
Reminder: it's ng add not yarn add.
It will do several things:
-
The @angular/localize package is installed.
Note that it is installed in devDependencies.
This also means that Angular i18n is done in the compile phase, not in runtime.
-
We just said that i18n happens in the compile phase, but that's not all. There's a small part of it that needs to be runtime-compatible.
This polyfill is used in these places.
-
tsconfig
You also need TypeScript to work with it, because runtime uses some global variables.
i18n Hello World
App Template
<h1 i18n>Hello World</h1>
Notice that the h1 has an "i18n" tag (attribute).
It is used to indicate that this "Hello World" needs to be translated into another language later.
Note: The simplest example is given here, and there will be more complicated ways to play below, so let's go through the simple round first.
Generate translation files
Execute command
ng extract-i18n --output-path src/locale
As we said above, Angular i18n happens in the compile phase.
This command creates a folder (src/locale) and a file ().
It's meant to be used by the translation lady.
It looks like this.
<?xml version="1.0" encoding="UTF-8" ?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <!-- 1. source-language="en-US" means that our source code is written in American English.--> <file source-language="en-US" datatype="plaintext" original=""> <body> <!-- 2. each sentence to be translated has a unique ID code--> <trans-unit id="4584092443788135411" datatype="html"> <!-- 3. source is the text we want to translate, which is the text in the App Template above <h1 i18n>Hello World</h1>.--> <source>Hello World</source> <context-group purpose="location"> <!-- 4. location indicates the text to be translated, which file and line it comes from--> <context context-type="sourcefile">src/app/</context> <context context-type="linenumber">1</context> </context-group> </trans-unit> </body> </file> </xliff>
Angular i18n scans all our files and extracts the parts that need to be translated, then produces .
Translate
Then we sent it to the translator lady.
She will translate for us in different languages, such as
(Simplified Chinese)
<?xml version="1.0" encoding="UTF-8" ?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en-US" datatype="plaintext" original=""> <body> <trans-unit id="4584092443788135411" datatype="html"> <source>Hello World</source> <!-- 1. Simplified Chinese added--> <target>How are you?,global</target> <context-group purpose="location"> <context context-type="sourcefile">src/app/</context> <context context-type="linenumber">1</context> </context-group> </trans-unit> </body> </file> </xliff>
(Japanese)
Setup and build application
Once the translation is done, it's time for the final part, ng build.
Before we build, we need to make some changes.
Informs about Angular originals, supported translations, and their file paths.
You also need to set projects.: true
Then it's time to build
ng build --localize
Warning
It's warning because I wrote a locale ID that it doesn't support.
A completelocale ID It should be "language-country", e.g.: zh-Hans for Simplified Chinese, CN for China, but it only supports writing the language zh-Hans, -CN doesn't work.
(statistics) correlationGithub Issue – Unable to find zh-Hant-TW or zh-TW locale in @angular/common
It doesn't matter, programmers don't care about warnings (if you get an error, just delete the country -CN and replace it with zh-Hans). Continue
A total of 3 folder "en-US", "ja-JP", "zh-Hans-CN" were built.
Each folder has its own , and so on.
All the code is exactly the same, except for this line
This rune is unicode, and the corresponding text is
Together, it means Konichiwa World, or Hello World in Japanese.
This is the unicode of Hello World in Simplified Chinese.
Run application
Open dist\i18n\browser, then Open with Live Server
effect
summarize
This is the simplest step by step process for Angular i18n.
There are a lot of details and gameplay I haven't covered yet, so let's fill them in one by one below, Let's go 🚀.
Everyday usage of i18n tags (attribute)
Let's take a look at the various everyday uses of the i18n label one by one.
i18n habit の tree shaking
Test Template has i18n tags.
However, the Test component is not used by any other component.
Q: Will ng extract-i18n scan for this label?
A: No, because there is a tree shaking concept for scanning.
i18n character acquired through long habit の same word same translate
<h1 i18n>Hello World</h1> <h1 i18n>Hello World</h1>
The two h1s have the exact same text.
They will be put into the same <trans-unit> and only need to be translated once.
Description, meaning, translate ID
<h1 i18n="description for this translate">Home</h1>
Tagged values can be used to describe the text to be translated.
This line will be filled in <trans-unit>.
In addition to description, we can also add meaning / title.
<h1 i18n="meaning for this translate|description for this translate">Home</h1>
Use pipe symbol | as a separator, preceded by meaning and followed by description.
Besides being a description, meaning has a unique function.
Let's take an example.
The English word "Home" can be translated as "home" or "home page".
Exactly which one you want to translate it into also depends on its context.
In other words, the feature we mentioned in the previous part, same word same translate, is an unspoken rule that can't be applied to all scenarios.
When this is the case, meaning can be used to distinguish the same text.
<h1 i18n="Header navigation|">Home</h1> <h1 i18n="A song name|">Home</h1>
effect
Although the text is the same, the meaning is different, so 2 <trans-unit> are generated.
One final note on translate ID.
This ID is unique, and Angular i18n will automatically generate a corresponding ID based on the text and meaning.
If we want to manage it ourselves, it's fine to write our own hardcode.
<h1 i18n="@@id-1">Home</h1> <h1 i18n="@@id-2">Home</h1>
effect
Although the text is the same and the meaning is the same, the IDs are different, so there will definitely be two separate <trans-units>.
With HTML and interpolation
<p i18n>Copyright © 2024 <a href="/">{{ companyName() }}</a>. All rights reserved</p>
p contains <a> and {{ interpolation }}, which are OK.
When translating, just flip for the text and don't change anything else.
Translate attribute value
title、aria-label such element attribute value It can also be translate。
<button i18n-aria-label aria-label="Example icon button with a vertical three dot icon" mat-icon-button> <mat-icon>more_vert</mat-icon> </button>
Just add the specified attribute name as suffix after the i18n tag.
Without element
If there is no element, and there is only text, then where to put the i18n tag?
The answer is <ng-container>.
<ng-container i18n>Hello World</ng-container>
ICU expressions
ICU expressions are used for conditional translation.
Let's look at examples to understand
handle conditional number – plural
<p i18n>{ peopleCount(), plural, =0 { no person } =1 { one person } other { {{ peopleCount() }} people } }</p>
Its syntax looks like this
{ parameter one, parameter two, parameter three }
Parameter one is condition, and peopleCount is a component property Signal<number>.
Parameter 2 has 2 values that can be filled in, one is plural and the other is select.
plural is for the number to do the judgment, select is for string to do the judgment, select will be taught below, we first look at plural.
Parameter three is the text to be presented under different numbers.
In the runtime phase, if peopleCount = 0, then "no person" is displayed.
If peopleCount = 1, then "one person" will be displayed.
peopleCount = some other number, then "{{ peopleCount() }} people" will be displayed.
With @if, it looks like this
<p i18n> @if(peopleCount() === 0) { no person } @else if (peopleCount() === 1) { one person } @else { {{ peopleCount() }} people } </p>
Obviously using ICU expressions would be more streamlined. (But plural's expressions are limited, it can only write =1, =5, not >5, <3 greater than less than these are not supported)
The translated document looks like this
Again, let's just translate the text and not change anything else.
handle conditional string – select
The select and plural structures are the same, except that the former is for strings and the latter is for numbers.
export class AppComponent { readonly gender = signal<'male' | 'female' | null>(null); }
<p i18n>{ gender(), select, male { male } female { female } other { other } }</p>
When gender is "male", "male" is displayed.
When gender is "female", "female" is displayed.
Show "other" when gender is neither "male" nor "female" (. null), display "Other".
Translate in script
What we've talked about above is doing translations in HTML.
What if my text is written in a script? How do I tag it with i18n?
The answer is to use $localize
export class AppComponent { constructor() { const value = $localize`Hello World`; (value); } }
$localize is a global variable.
The type introduced is for it
$localize is equivalent to the HTML i18n tag, the usage is similar, and the translated document is the same.
Here's how meaning, description, and id look like
const value = $localize`:meaning|description@@id:Hello World`;
Split with a : semicolon.
The only major difference is that $localize does not support ICU expressions.
If we need conditional we can just use the usual if else swtich, for example:
// <p i18n>{ peopleCount(), plural, =0 { no person } =1 { one person } other { {{ peopleCount() }} people } }</p> const peopleCount = signal(0); const value = peopleCount() === 0 ? $localize`no person` : peopleCount() === 1 ? $localize`one person` : $localize`${peopleCount()} people`; (value);
summarize
These are the everyday uses of the i18n tag.
ng serve for i18n application
All we've talked about above is the final release of ng build.
Can ng serve enable the i18n application during the development phase?
Yes, but only one of the locale can be selected.
Go to Specify locale
Instead of true, it will be an array, and only one locale can be placed in the array.
Then ng serve and you're done.
Get current locale ID
By injecting LOCALE_ID, we can find out what locale we are currently in.
export class AppComponent { constructor() { (inject(LOCALE_ID)); // zh-Hans } }
Without i18n, it defaults to "en-US" (reminder: it's not based on the browser settings, it's hardcode en-US).
About base href
All builds come with <base href="/locale/">
<base href> what's the use, you can see thethis one。
Why does Angular i18n add locale to the base href?
Because it wants to make it easier for us to deploy, I'll take Core as an example.
Core routinely puts all ng builds in the wwwroot folder.
Then we'll do the routing.
Simply put, when a user accesses /zh-Hans-CN/**/* they are accessing /zh-Hans-CN/.
In combination with the base href, the path is /en-US/polyfills-js.
From wwwroot down to "\en-US\", this path is correct.
If the base href is "/", the path becomes "/".
Then this document has to be available at wwwroot\.
As you can see, adding a base href would be more reasonable and convenient.
If we don't like what it's doing to us, we can also go to the RIP and configure it.
This way the ng build comes out as <base href="/" >.
DatePipe with Locale
The DatePipe will change depending on the locale, for example
<p>{{ today() | date }}</p>
In the case of zh-Hans, it has the effect of
The same is true for formatDate
constructor() { const today = new Date(); const format = 'mediumDate'; const locale = inject(LOCALE_ID); (formatDate(today, format, locale)); // September 17, 2024 }
How does formatDate do translate at the bottom?
First of all, it doesn't use the excursion's nativeIntlI'm not sure why Angular wrote its own logic (why would Angular write its own logic instead of using the native one? I'm not sure, maybe it was because Inlt wasn't well supported at the time? In any case, my feeling is that Angular will probably switch to native Intl in the future).
With ɵfindLocaleData (which is used at the bottom of formatDate), we can get a lot of translation content
import { ɵfindLocaleData } from '@angular/core'; constructor() { (ɵfindLocaleData('zh-Hans')); // Note: It can't be zh-Hans-CN here, because Angular's locale data doesn't have zh-Hans-CN, only zh-Hans /. \ }
effect
It contains the date formats and languages needed for formatDate.
Let's try find other locale again
(ɵfindLocaleData('ja'));
It's an error.
The reason for this is simple: Angular doesn't load all locale information by default. zh-Hans can be found because we made i18n and specified that ng serve is zh-Hans.
It doesn't load automatically, but we can manually load it for it.
import { registerLocaleData, } from '@angular/common'; import jaLocaleData from '@angular/common/locales/ja'; registerLocaleData(jaLocaleData, 'ja');
import Japanese data and register it to localeData.
That's how you find it.
(ɵfindLocaleData('ja'));
effect
CurrencyPipe with Locale
Different countries use different currencies, and locale includes currency in addition to language, date format, and of course, currency.
In the previous part, when we looked at the locale data, the monetary information was also included.
Let's try CurrencyPipe.
<p>{{ 500 | currency }}</p>
effect
Ebony... How come it's not RMB ❓🤔
on account ofGithub Issue – Currency pipe and locale
Simply put, they don't think it makes sense to automatically change the currency symbol but not the value, so they just give the developer all the responsibility.
There are two ways we can do currency pipe with locale.
The first is to use the getLocaleCurrencyName/Code/Symbol function
import { getLocaleCurrencyCode, getLocaleCurrencyName, getLocaleCurrencySymbol } from '@angular/common'; const code = getLocaleCurrencyCode('zh-Hans'); const name = getLocaleCurrencyName('zh-Hans'); const symbol = getLocaleCurrencySymbol('zh-Hans'); ([code, name, symbol]); // ['CNY', 'yuan', '¥']
These three functions use the ɵfindLocaleData function at the bottom, which we covered in the previous part.
Also, getLocaleCurrencyName/Code/Symbol is currently deprecated.
Angular recommends that we use the native Intl for names and symbols.
getLocaleCurrencyCode can't be implemented using Intl, Angular's suggestion is for us to write our own mapping list 😮.
The second approach is to follow Angular's advice and use the native Intl.
const locale = 'zh-Hans'; const code = getLocaleCurrencyCode(locale)!; // Intl doesn't generate CNYs from zh-Hans, so we have to write our own mapping lists or continue to use its deprecated interfaces. const symbolFormatter = new (locale, { style: 'currency', currency: code, currencyDisplay: 'symbol' }); const symbol = (0).find(part => === 'currency')!.value; // ¥ const displayNames = new ([locale], { type: 'currency' }); const name = (code); ([code, symbol, name]); // ['CNY', '¥', 'yuan']
summarize
This post briefly introduces the Angular i18n scheme, without going into the principles or strolling through the source code.
Since I've never personally used it in a project, I'll hopefully have the opportunity in the future, and then I'll delve deeper into it.
Also, for everyday projects, I use the Core – Globalization & Localization。
Comparing the two, the biggest difference is that Core's translation documentation is broken up, with one translation document for each page, or even each component.
Instead of putting the entire project's component information into one document, as Angular does.
It feels like Angular can be messy to maintain, especially if there are changes to the content of the site or application.
catalogs
Previous article.Angular 18+ Advanced Tutorials – Memory leak, unsubscribe, onDestroy
Next TODO
To see the table of contents, go toAngular 18+ Advanced Tutorial - Table of Contents
Like it please click recommend 👍 and if you find the tutorial content to be out of context with the new version please comment to let me know. happy coding 😊 💻