- Angular
Angular多言語対応(i18n)の手順|$localizeと翻訳ファイル
Angular の多言語対応(i18n)とは、テンプレートやコードの文言に「翻訳対象」の印を付け、翻訳ファイル(xlf)を用意して、言語ごとのアプリをビルドする仕組みです。 公式パッケージの @angular/localize を入れると、印を付けた文言の抽出(ng extract-i18n)から言語別ビルド(ng build --localize)までを Angular CLI だけで進められます。日付・通貨・数値の表示形式も、ロケール(言語と地域の組み合わせ)に合わせて自動で切り替わります。
この記事では、Angular 22.2.1 で日本語の画面を作り、英語版を追加するまでをコマンドの出力と生成されたファイルの中身つきで通しで載せます。i18n 属性・$localize・ICU 式(plural / select)の書き方早見表、LOCALE_ID が ja-JP のときと en-US のときのパイプ出力比較表、翻訳漏れのときに出る警告文と直し方もまとめました。
確認環境:Linux(x64)、Node.js 24.21.0、Angular CLI / Angular / @angular/localize 22.2.1、TypeScript 6.0.3、Playwright 1.56.0 の Chromium。ng new の既定構成で作成し、ビルド結果をブラウザで開いて表示を確かめています。
Angular i18n の早見表
| 手順 | コマンド・書くもの | できあがるもの |
|---|---|---|
| 1. パッケージを入れる | ng add @angular/localize |
package.json・angular.json・tsconfig・main.ts が更新される |
| 2. 文言に印を付ける | テンプレートは i18n 属性、TypeScript は $localize |
翻訳対象が決まる |
| 3. 元の言語を設定する | angular.json の i18n.sourceLocale を ja に |
抽出ファイルの言語と、日付などの表示形式が日本語になる |
| 4. 文言を抽出する | ng extract-i18n --output-path src/locale |
src/locale/messages.xlf |
| 5. 翻訳ファイルを作る | messages.xlf をコピーして target 要素を足す |
src/locale/messages.en-US.xlf |
| 6. 翻訳ファイルを登録する | angular.json の i18n.locales |
言語とファイルの対応 |
| 7. 言語別にビルドする | ng build --localize |
dist の下に ja と en-US のフォルダー |
| 書き方 | 使う場所 | 例 |
|---|---|---|
i18n 属性 |
要素の中の文言 | h1 要素に i18n="@@pageTitle" |
i18n-属性名 |
属性の値(placeholder、title、alt など) | i18n-placeholder="@@searchPlaceholder" |
$localize |
TypeScript の中の文言 | テンプレート文字列の前に $localize を付ける |
| ICU 式の plural | 件数で文言を変える | =0、=1、other で分ける |
| ICU 式の select | 値で文言を変える | admin、member、other で分ける |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Angular多言語対応(i18n)の手順|$localizeと翻訳ファイル」
題材の画面を用意する
備品一覧の画面を題材にします。文言は日本語で書き、あとから英語版を追加します。
// src/app/app.ts
import { Component, signal } from '@angular/core';
import { CurrencyPipe, DatePipe, DecimalPipe, PercentPipe } from '@angular/common';
@Component({
selector: 'app-root',
imports: [DatePipe, CurrencyPipe, DecimalPipe, PercentPipe],
templateUrl: './app.html',
})
export class App {
readonly count = signal(0);
readonly role = signal<'admin' | 'member' | 'guest'>('admin');
readonly userName = '田中';
readonly greeting = $localize`:ヘッダーのあいさつ@@greeting:こんにちは、${this.userName}:name:さん`;
readonly updatedAt = new Date('2026-10-03T05:07:09Z');
readonly price = 1234567.891;
readonly rate = 0.256;
add(): void {
this.count.update((n) => n + 1);
}
}
<!-- src/app/app.html -->
<h1 i18n="画面のタイトル|備品一覧ページの見出し@@pageTitle">備品一覧</h1>
<p id="greeting">{{ greeting }}</p>
<input id="search" placeholder="備品名で検索" i18n-placeholder="@@searchPlaceholder" />
<button id="add" (click)="add()" i18n="@@addButton">備品を追加</button>
<p id="count" i18n="@@itemCount">{count(), plural, =0 {備品はまだありません} =1 {備品が1件あります} other {備品が{{ count() }}件あります}}</p>
<p id="role" i18n="@@roleLabel">権限:{role(), select, admin {管理者} member {会員} other {ゲスト}}</p>
<table id="formats">
<tr><td>date</td><td>{{ updatedAt | date: undefined : '+0900' }}</td></tr>
<tr><td>date short</td><td>{{ updatedAt | date: 'short' : '+0900' }}</td></tr>
<tr><td>date medium</td><td>{{ updatedAt | date: 'medium' : '+0900' }}</td></tr>
<tr><td>date longDate</td><td>{{ updatedAt | date: 'longDate' : '+0900' }}</td></tr>
<tr><td>date fullDate</td><td>{{ updatedAt | date: 'fullDate' : '+0900' }}</td></tr>
<tr><td>date y/MM/dd HH:mm</td><td>{{ updatedAt | date: 'y/MM/dd HH:mm' : '+0900' }}</td></tr>
<tr><td>date EEEE</td><td>{{ updatedAt | date: 'EEEE' : '+0900' }}</td></tr>
<tr><td>currency</td><td>{{ price | currency }}</td></tr>
<tr><td>currency JPY</td><td>{{ price | currency: 'JPY' }}</td></tr>
<tr><td>currency USD</td><td>{{ price | currency: 'USD' }}</td></tr>
<tr><td>number</td><td>{{ price | number }}</td></tr>
<tr><td>number 1.0-0</td><td>{{ price | number: '1.0-0' }}</td></tr>
<tr><td>percent</td><td>{{ rate | percent }}</td></tr>
<tr><td>percent 1.1-1</td><td>{{ rate | percent: '1.1-1' }}</td></tr>
</table>
date パイプの3つ目の引数 '+0900' は、表示に使うタイムゾーンです。見る人の端末の設定に関係なく日本時間で表示するために付けています。
手順1:ng add @angular/localize でパッケージを入れる
@angular/localize を入れる前にビルドすると、$localize を使った行で止まります。
$ ng build
✘ [ERROR] TS2304: Cannot find name '$localize'. [plugin angular-compiler]
src/app/app.ts:13:22:
13 │ readonly greeting = $localize`:ヘッダーのあいさつ@@gree...
╵ ~~~~~~~~~
ng add で導入します。
$ ng add @angular/localize
› Using package manager: npm
✔ Determining Package Manager
› Found compatible package version: 22.2.1.
✔ Searching for compatible package version
✔ Loading package information
✔ Installing package
UPDATE src/main.ts (267 bytes)
UPDATE tsconfig.app.json (429 bytes)
UPDATE tsconfig.spec.json (436 bytes)
UPDATE angular.json (1978 bytes)
4つのファイルが更新されました。差分を見ると、次の内容が足されています。
| ファイル | 足された内容 | 役割 |
|---|---|---|
| package.json | "@angular/localize": "^22.2.1" |
パッケージ本体 |
| angular.json | build の options に "polyfills": ["@angular/localize/init"] |
実行時に $localize を使えるようにする |
| tsconfig.app.json | "types": ["@angular/localize"] |
$localize の型を読み込む |
| tsconfig.spec.json | types に "@angular/localize" を追加 |
テストでも型を読み込む |
| src/main.ts | 先頭に /// <reference types="@angular/localize" /> |
型の参照 |
これでビルドが通り、この時点の画面は日本語の文言がそのまま表示されます(翻訳ファイルがまだないため)。
手順2:文言に印を付ける(i18n 属性・$localize・ICU 式)
i18n 属性の書き方
i18n 属性の値には、翻訳する人向けの補足と ID を書けます。書式は次のとおりです。
i18n="意味|説明@@ID"
| 書き方 | 例 | 抽出ファイルでの扱い |
|---|---|---|
| 属性だけ | i18n |
ID は自動で振られる |
| 説明だけ | i18n="備品一覧ページの見出し" |
description の note になる |
| 意味と説明 | i18n="画面のタイトル|備品一覧ページの見出し" |
meaning と description の note になる |
| ID だけ | i18n="@@pageTitle" |
trans-unit の id が pageTitle になる |
| 全部 | i18n="画面のタイトル|備品一覧ページの見出し@@pageTitle" |
上の3つすべて |
ID(@@ のあと)を自分で付けておくと、文言を少し直しても ID が変わらず、翻訳ファイルとの対応が保たれます。 属性の値を翻訳するときは、i18n-placeholder のように「i18n- と属性名」をつなげた属性を足します。
$localize の書き方
TypeScript の中の文言は、テンプレート文字列の前に $localize を付けます。
readonly greeting = $localize`:ヘッダーのあいさつ@@greeting:こんにちは、${this.userName}:name:さん`;
| 部分 | 意味 |
|---|---|
先頭の :ヘッダーのあいさつ@@greeting: |
コロンで囲んだ補足。i18n 属性と同じ「意味、説明、@@ID」の書式 |
${this.userName} |
埋め込む値 |
直後の :name: |
埋め込む値に付ける名前。翻訳ファイルではこの名前で場所を示す |
ICU 式(plural / select)の書き方
件数や値によって文言を変えるときは、ICU 式(波かっこで書く条件つきの文言)を使います。
<p i18n="@@itemCount">{count(), plural, =0 {備品はまだありません} =1 {備品が1件あります} other {備品が{{ count() }}件あります}}</p>
<p i18n="@@roleLabel">権限:{role(), select, admin {管理者} member {会員} other {ゲスト}}</p>
| 種類 | 書式 | 分け方 |
|---|---|---|
| plural | 値、plural、分岐の順にカンマで区切る | =0、=1 のような数の指定と、zero、one、two、few、many、other のカテゴリ |
| select | 値、select、分岐の順にカンマで区切る | 値の文字列(admin、member など)と other |
ボタンを2回押したときの表示は次のとおりです。
count=["備品はまだありません","備品が1件あります","備品が2件あります"]
role=権限:管理者
どちらの式も other を必ず書いておくと、どの分岐にも当てはまらない値のときの表示が決まります。 分岐の中の文言には、二重波かっこの埋め込みも書けます。
手順3:元の言語(sourceLocale)を ja にする
Angular は、何も設定しないと元の言語を en-US として扱います。日本語で書いたアプリでは、抽出の前に angular.json の i18n.sourceLocale を ja にしておきます。
{
"projects": {
"demo": {
"i18n": {
"sourceLocale": "ja"
}
}
}
}
この設定を入れずに抽出すると、日本語の文言なのに元の言語が en-US と記録されます。
<file source-language="en-US" datatype="plaintext" original="ng2.template">
sourceLocale を ja にすると、次のように変わります。
<file source-language="ja" datatype="plaintext" original="ng2.template">
sourceLocale には、もう1つ効果があります。ng build(--localize なし)の結果も、html 要素の lang 属性が ja になり、日付の表示形式が日本語になりました。
--- sourceLocale を設定する前(ng build)
lang=en
date medium => Oct 3, 2026, 2:07:09 PM
--- sourceLocale を ja にしたあと(ng build)
lang=ja
date medium => 2026/10/03 14:07:09
手順4:ng extract-i18n で文言を抽出する
$ ng extract-i18n --output-path src/locale
Application bundle generation complete. [2.909 seconds]
Extraction Complete. (Messages: 7)
src/locale/messages.xlf が1つ生成されます。中身の主な部分です。
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="ja" datatype="plaintext" original="ng2.template">
<body>
<trans-unit id="pageTitle" datatype="html">
<source>備品一覧</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.html</context>
<context context-type="linenumber">1,4</context>
</context-group>
<note priority="1" from="description">備品一覧ページの見出し</note>
<note priority="1" from="meaning">画面のタイトル</note>
</trans-unit>
<trans-unit id="itemCount" datatype="html">
<source>{VAR_PLURAL, plural, =0 {備品はまだありません} =1 {備品が1件あります} other {備品が<x id="INTERPOLATION"/>件あります}}</source>
(context-group は省略)
</trans-unit>
<trans-unit id="roleLabel" datatype="html">
<source>権限:<x id="ICU" equiv-text="{role(), select, admin {管理者} member {会員} other {ゲスト}}" xid="2736741166673655625"/></source>
</trans-unit>
<trans-unit id="2736741166673655625" datatype="html">
<source>{VAR_SELECT, select, admin {管理者} member {会員} other {ゲスト}}</source>
</trans-unit>
<trans-unit id="greeting" datatype="html">
<source>こんにちは、<x id="name" equiv-text="this.userName"/>さん</source>
<note priority="1" from="description">ヘッダーのあいさつ</note>
</trans-unit>
</body>
</file>
</xliff>
| 抽出結果で見るところ | 内容 |
|---|---|
trans-unit の id |
@@ で付けた ID がそのまま入る |
source |
元の文言 |
note |
i18n 属性に書いた意味(meaning)と説明(description) |
x 要素 |
埋め込みの場所。二重波かっこの埋め込みは INTERPOLATION、$localize で名前を付けた値はその名前(name)になる |
| VAR_PLURAL / VAR_SELECT | ICU 式の値の部分。式の中身は置き換えられている |
| 数字だけの id | 文の途中にある ICU 式は、文とは別の trans-unit として切り出され、ID が自動で振られる |
画面には文言が6か所ですが、「権限:」の文と、その中の select 式が別々に数えられるため、Messages は 7 になっています。
手順5:翻訳ファイル(messages.en-US.xlf)を作る
messages.xlf をコピーして messages.en-US.xlf を作り、各 trans-unit の source の下に target を足します。file 要素には target-language も書いておきます。
<file source-language="ja" target-language="en-US" datatype="plaintext" original="ng2.template">
<body>
<trans-unit id="pageTitle" datatype="html">
<source>備品一覧</source>
<target>Equipment list</target>
</trans-unit>
<trans-unit id="searchPlaceholder" datatype="html">
<source>備品名で検索</source>
<target>Search by name</target>
</trans-unit>
<trans-unit id="addButton" datatype="html">
<source>備品を追加</source>
<target>Add item</target>
</trans-unit>
<trans-unit id="itemCount" datatype="html">
<source>{VAR_PLURAL, plural, =0 {備品はまだありません} =1 {備品が1件あります} other {備品が<x id="INTERPOLATION"/>件あります}}</source>
<target>{VAR_PLURAL, plural, =0 {No items yet} =1 {1 item} other {<x id="INTERPOLATION"/> items}}</target>
</trans-unit>
<trans-unit id="roleLabel" datatype="html">
<source>権限:<x id="ICU" equiv-text="{role(), select, admin {管理者} member {会員} other {ゲスト}}" xid="2736741166673655625"/></source>
<target>Role: <x id="ICU" equiv-text="{role(), select, admin {管理者} member {会員} other {ゲスト}}" xid="2736741166673655625"/></target>
</trans-unit>
<trans-unit id="2736741166673655625" datatype="html">
<source>{VAR_SELECT, select, admin {管理者} member {会員} other {ゲスト}}</source>
<target>{VAR_SELECT, select, admin {Administrator} member {Member} other {Guest}}</target>
</trans-unit>
<trans-unit id="greeting" datatype="html">
<source>こんにちは、<x id="name" equiv-text="this.userName"/>さん</source>
<target>Hello, <x id="name" equiv-text="this.userName"/>!</target>
</trans-unit>
</body>
</file>
(見やすくするため context-group と note は省いています。残したままでも動きます。)
| 翻訳するときの決まり | 内容 |
|---|---|
x 要素はそのまま写す |
位置は文の中で動かしてよい。英語では items の前に移している |
| VAR_PLURAL、VAR_SELECT、plural、select、分岐の名前(=0、admin など)は変えない | 波かっこの中の文言だけを訳す |
| 文の途中の ICU 式は2か所を訳す | 文の側(roleLabel)と、式の側(数字の id) |
手順6:angular.json に翻訳ファイルを登録する
i18n の locales に、ロケール ID と翻訳ファイルの対応を書きます。
{
"projects": {
"demo": {
"i18n": {
"sourceLocale": "ja",
"locales": {
"en-US": {
"translation": "src/locale/messages.en-US.xlf"
}
}
}
}
}
}
ロケール ID は「言語-地域」の形で書きます(Angular の公式ドキュメントでは、Unicode の CLDR という仕様に沿った ID と説明されています)。日本語は ja または ja-JP、英語(米国)は en-US です。
手順7:ng build --localize で言語別にビルドする
$ ng build --localize
Initial chunk files | Names | Raw size | Estimated transfer size
main-YAZDOGIT.js | main | 143.99 kB | 44.68 kB
polyfills-KC2ARXRM.js | polyfills | 88 bytes | 88 bytes
styles-5INURTSO.css | styles | 0 bytes | 0 bytes
| Initial total | 144.08 kB | 44.77 kB
Application bundle generation complete. [2.515 seconds]
出力先には、言語ごとのフォルダーができます。
dist/demo/browser/en-US/index.html
dist/demo/browser/en-US/main-YAZDOGIT.js
dist/demo/browser/en-US/polyfills-KC2ARXRM.js
dist/demo/browser/en-US/styles-5INURTSO.css
dist/demo/browser/ja/index.html
dist/demo/browser/ja/main-YAZDOGIT.js
dist/demo/browser/ja/polyfills-KC2ARXRM.js
dist/demo/browser/ja/styles-5INURTSO.css
それぞれの index.html は、lang 属性と base 要素の href が言語に合わせて書き換わっています。
<!-- dist/demo/browser/en-US/index.html -->
<html lang="en-US" dir="ltr" data-beasties-container>
<head>
<meta charset="utf-8">
<title>Demo</title>
<base href="/en-US/">
<!-- dist/demo/browser/ja/index.html -->
<html lang="ja" dir="ltr" data-beasties-container>
<head>
<meta charset="utf-8">
<title>Demo</title>
<base href="/ja/">
/ja/ と /en-US/ をブラウザで開いた結果です。
--- /ja/ lang=ja base=/ja/
h1=備品一覧 | greeting=こんにちは、田中さん | placeholder=備品名で検索 | button=備品を追加
count=["備品はまだありません","備品が1件あります","備品が2件あります"] role=権限:管理者
--- /en-US/ lang=en-US base=/en-US/
h1=Equipment list | greeting=Hello, 田中! | placeholder=Search by name | button=Add item
count=["No items yet","1 item","2 items"] role=Role: Administrator
Angular の i18n は、ビルドの時点で文言を差し替えた「言語ごとのアプリ」を作る方式です。 画面上の言語切り替えは、/ja/ と /en-US/ のあいだを行き来するリンクとして用意します。
LOCALE_ID とパイプの出力比較(ja と en-US)
同じテンプレートを、ja のビルドと en-US のビルドで開いたときの表示です。値は 2026-10-03 14:07:09(日本時間)、1234567.891、0.256 です。
| パイプ | ja | en-US |
|---|---|---|
| date(引数なし) | 2026/10/03 | Oct 3, 2026 |
| date:'short' | 2026/10/03 14:07 | 10/3/26, 2:07 PM |
| date:'medium' | 2026/10/03 14:07:09 | Oct 3, 2026, 2:07:09 PM |
| date:'longDate' | 2026年10月3日 | October 3, 2026 |
| date:'fullDate' | 2026年10月3日土曜日 | Saturday, October 3, 2026 |
| date:'y/MM/dd HH:mm' | 2026/10/03 14:07 | 2026/10/03 14:07 |
| date:'EEEE' | 土曜日 | Saturday |
| currency(引数なし) | $1,234,567.89 | $1,234,567.89 |
| currency:'JPY' | ¥1,234,568 | ¥1,234,568 |
| currency:'USD' | $1,234,567.89 | $1,234,567.89 |
| number(引数なし) | 1,234,567.891 | 1,234,567.891 |
| number:'1.0-0' | 1,234,568 | 1,234,568 |
| percent(引数なし) | 26% | 26% |
| percent:'1.1-1' | 25.6% | 25.6% |
表から読み取れることは次の3つです。
- 日付は、short や longDate のような「名前つきの形式」を使うと、ロケールに合わせて並び順も月名も変わる。 'y/MM/dd HH:mm' のように形を直接書くと、どのロケールでも同じ並びになる(曜日や月名の言葉だけがロケールに従う)
- currency の引数なしは、ja でも $ のまま。 ロケールを変えても通貨の種類は変わらない
- 円記号は、ja では全角の ¥、en-US では半角の ¥ になる
既定の通貨を円にする(DEFAULT_CURRENCY_CODE)
currency パイプの引数なしの通貨は、DEFAULT_CURRENCY_CODE で変えられます。
// src/app/app.config.ts
import { ApplicationConfig, DEFAULT_CURRENCY_CODE, provideBrowserGlobalErrorListeners } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
{ provide: DEFAULT_CURRENCY_CODE, useValue: 'JPY' },
],
};
currency => ¥1,234,568
currency JPY => ¥1,234,568
currency USD => $1,234,567.89
翻訳は使わず、表示形式だけ日本語にする(LOCALE_ID と registerLocaleData)
英語版を作る予定はなく、日付の表示形式だけ日本語にしたい場合は、手順3の sourceLocale を ja にする方法がいちばん手軽です。angular.json を変えずにコードで指定する方法もあります。そのときは LOCALE_ID の指定と、ロケールデータの登録(registerLocaleData)の2つがそろって動きます。
LOCALE_ID だけを指定した場合(i18n の設定をしていない状態)は、日付の欄が空になり、コンソールに次のエラーが出ました(開発ビルドでの表示)。
// エラーになる例:LOCALE_ID だけを指定
providers: [
provideBrowserGlobalErrorListeners(),
{ provide: LOCALE_ID, useValue: 'ja-JP' },
],
ERROR RuntimeError: NG0701: Missing locale data for the locale "ja-jp".
ERROR RuntimeError: NG02100: InvalidPipeArgument: 'NG0701: Missing locale data for the locale "ja-JP".' for pipe '_DatePipe'
本番ビルドでは文言が短くなり、「ERROR g: NG0701」「ERROR g: NG02100」のように番号だけが出ます。registerLocaleData を足すと表示されます。
// src/app/app.config.ts
import { ApplicationConfig, LOCALE_ID, provideBrowserGlobalErrorListeners } from '@angular/core';
import { registerLocaleData } from '@angular/common';
import localeJa from '@angular/common/locales/ja';
registerLocaleData(localeJa);
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
{ provide: LOCALE_ID, useValue: 'ja-JP' },
],
};
date => 2026/10/03
date medium => 2026/10/03 14:07:09
date fullDate => 2026年10月3日土曜日
currency JPY => ¥1,234,568
| やりたいこと | 方法 |
|---|---|
| 日本語のアプリで、日付などを日本語の形式にしたい | angular.json の i18n.sourceLocale を ja にする(コードの変更なし) |
| 言語ごとにアプリを分けたい | ng build --localize(LOCALE_ID は言語ごとに自動で設定される) |
| コードでロケールを指定したい | LOCALE_ID の provider と registerLocaleData の両方を書く |
翻訳漏れのときに出る警告と直し方
翻訳ファイルから addButton の trans-unit を1つ消してビルドすると、警告が出ます。
$ ng build --localize
Application bundle generation complete. [2.296 seconds]
▲ [WARNING] No translation found for "addButton" ("備品を追加").
Angular 22.2.1 のビルドでは、この「No translation found for」という文で知らせてくれます。ビルドは完了し、英語版のその場所には元の日本語がそのまま表示されました。
--- /en-US/
h1=Equipment list | greeting=Hello, 田中! | placeholder=Search by name | button=備品を追加
直し方は、警告に出ている ID(addButton)の trans-unit を翻訳ファイルに足して target を書くことです。
翻訳漏れをビルドエラーにする(i18nMissingTranslation)
リリース前に必ず気づけるよう、翻訳漏れでビルドを止める設定もあります。build の options に i18nMissingTranslation を足します。
{
"projects": {
"demo": {
"architect": {
"build": {
"options": {
"i18nMissingTranslation": "error"
}
}
}
}
}
}
$ ng build --localize
Application bundle generation failed. [2.591 seconds]
✘ [ERROR] No translation found for "addButton" ("備品を追加").
| 値 | 動き |
|---|---|
warning(既定) |
警告を出してビルドを続ける |
error |
エラーにしてビルドを止める |
ignore |
何も出さない |
文言を足したあとの更新の流れ
ng extract-i18n が書き出すのは messages.xlf だけで、messages.en-US.xlf はそのままです。文言を足したり直したりしたあとは、次の順に進めます。
ng extract-i18n --output-path src/localeで messages.xlf を作り直す- 増えた trans-unit を messages.en-US.xlf に写して
targetを書く ng build --localizeを実行し、「No translation found for」が出ないことを見る
@@ で ID を付けておくと、手順2で「どれが増えたか」を ID で見比べられます。
開発中に英語版を表示する(ng serve)
Angular の公式ドキュメントでは、開発サーバーは一度に1つのロケールに対応すると説明されています。英語版を見るための構成を angular.json に足します。
{
"projects": {
"demo": {
"architect": {
"build": {
"configurations": {
"en": {
"localize": ["en-US"]
}
}
},
"serve": {
"configurations": {
"en": {
"buildTarget": "demo:build:development,en"
}
}
}
}
}
}
}
$ ng serve --configuration=en --port 8811
Application bundle generation complete. [2.845 seconds]
Watch mode enabled. Watching for file changes...
➜ Local: http://localhost:8811/
ブラウザで開くと、英語版が表示されました。
lang=en-US h1=Equipment list date=date fullDateSaturday, October 3, 2026
つまずきやすい点と直し方
| 起きること | 原因 | 直し方 |
|---|---|---|
| TS2304: Cannot find name '$localize' | @angular/localize が入っていない | ng add @angular/localize |
| 抽出ファイルが source-language="en-US" になる | sourceLocale の設定がない | angular.json の i18n.sourceLocale を ja に |
| 日付が Oct 3, 2026 のような英語の形式で出る | ロケールが既定の en-US のまま | sourceLocale を ja にする |
| NG0701: Missing locale data | LOCALE_ID だけを指定している | registerLocaleData を足す、または sourceLocale を使う |
| 英語版の一部が日本語のまま | 翻訳ファイルに target がない | 警告「No translation found for」の ID を見て足す |
| 金額が $ で出る | currency の既定の通貨は USD | currency:'JPY' と書く、または DEFAULT_CURRENCY_CODE |
| 文の途中の ICU 式が訳されない | 数字の id の trans-unit を訳していない | 文の側と式の側の2か所を訳す |
よくある質問
Q. ロケール ID は ja と ja-JP のどちらを書けばよいですか? A. どちらでも日本語の表示形式になります。今回は sourceLocale に ja、LOCALE_ID の例に ja-JP を使い、どちらも「2026/10/03」「2026年10月3日土曜日」と表示されました。ロケール ID は「言語」または「言語-地域」の形で書きます。
Q. 画面を再読み込みせずに言語を切り替えられますか? A. Angular 標準の i18n は、言語ごとに別のアプリをビルドする方式です。今回も ja と en-US のフォルダーが別々に出力されました。切り替えは /ja/ と /en-US/ のあいだを移動するリンクで行います。
Q. 翻訳ファイルの形式は xlf 以外も選べますか? A. 選べます。ng extract-i18n --help で見ると、--format の選択肢は arb、json、legacy-migrate、xlf、xlf2、xlif、xliff、xliff2、xmb で、既定は今回使った xlf です。--format json で抽出すると、locale と translations(ID と文言の組)を持つ messages.json が出力されました。
Q. ng build --localize を付けないとどうなりますか? A. 元の言語のアプリが1つだけ出力されます。今回の確認では、dist/demo/browser の直下に index.html ができ、lang 属性は sourceLocale の ja になりました。
まとめ
- 手順は7つ:ng add @angular/localize、印を付ける、sourceLocale、ng extract-i18n、翻訳ファイル、locales の登録、ng build --localize
- テンプレートは i18n 属性、属性の値は i18n-属性名、TypeScript は $localize で印を付ける
- 件数や値で文言を変えるときは ICU 式(plural / select)を使い、other を必ず書く
- 日本語で書いたアプリは sourceLocale を ja にすると、抽出ファイルの言語と日付の表示形式がそろう
- ng build --localize は言語ごとのフォルダーを出力し、lang 属性と base の href も書き換える
- 日付は名前つきの形式(short、longDate など)を使うと、ロケールごとの並びになる
- 翻訳漏れは「No translation found for」の警告で分かり、i18nMissingTranslation を error にするとビルドが止まる
参考・出典
- Angular 公式ドキュメント(i18n:翻訳対象の印の付け方) https://angular.jp/guide/i18n/prepare
- Angular 公式ドキュメント(i18n:ロケール ID) https://angular.jp/guide/i18n/locale-id
- Angular 公式ドキュメント「アプリへの翻訳の統合」 https://angular.jp/guide/i18n/merge
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Angular多言語対応(i18n)の手順|$localizeと翻訳ファイル」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。