9360aa2d27
The quotes rule had to be disabled for e2e tests generated from ngdoc because dgeni templates use double quotes as string delimiters. Since we can't have guarantees that dgeni template wrappers will follow the same JS code style the Angular 1 repo uses, we should find a way to enforce our ESLint setup only for the parts in this repo, perhaps via prepending a generated `/* eslint-enable OUR_RULES */` pragma. Closes #15011
467 lines
20 KiB
Plaintext
467 lines
20 KiB
Plaintext
@ngdoc overview
|
|
@name i18n and l10n
|
|
@sortOrder 520
|
|
@description
|
|
|
|
# i18n and l10n
|
|
|
|
Internationalization (i18n) is the process of developing products in such a way that they can be
|
|
localized for languages and cultures easily. Localization (l10n), is the process of adapting
|
|
applications and text to enable their usability in a particular cultural or linguistic market. For
|
|
application developers, internationalizing an application means abstracting all of the strings and
|
|
other locale-specific bits (such as date or currency formats) out of the application. Localizing an
|
|
application means providing translations and localized formats for the abstracted bits.
|
|
|
|
|
|
## How does Angular support i18n/l10n?
|
|
|
|
Angular supports i18n/l10n for {@link ng.filter:date date}, {@link ng.filter:number number} and
|
|
{@link ng.filter:currency currency} filters.
|
|
|
|
Localizable pluralization is supported via the {@link ng.directive:ngPluralize `ngPluralize`
|
|
directive}. Additionally, you can use {@link guide/i18n#messageformat-extensions MessageFormat extensions} to
|
|
`$interpolate` for localizable pluralization and gender support in all interpolations via the
|
|
`ngMessageFormat` module.
|
|
|
|
All localizable Angular components depend on locale-specific rule sets managed by the {@link
|
|
ng.$locale `$locale` service}.
|
|
|
|
There a few examples that showcase how to use Angular filters with various locale rule sets in the
|
|
[`i18n/e2e` directory](https://github.com/angular/angular.js/tree/master/i18n/e2e) of the Angular
|
|
source code.
|
|
|
|
|
|
## What is a locale ID?
|
|
|
|
A locale is a specific geographical, political, or cultural region. The most commonly used locale
|
|
ID consists of two parts: language code and country code. For example, `en-US`, `en-AU`, and
|
|
`zh-CN` are all valid locale IDs that have both language codes and country codes. Because
|
|
specifying a country code in locale ID is optional, locale IDs such as `en`, `zh`, and `sk` are
|
|
also valid. See the [ICU](http://userguide.icu-project.org/locale) website for more information
|
|
about using locale IDs.
|
|
|
|
|
|
## Supported locales in Angular
|
|
|
|
Angular separates number and datetime format rule sets into different files, each file for a
|
|
particular locale. You can find a list of currently supported locales
|
|
[here](https://github.com/angular/angular.js/tree/master/src/ngLocale)
|
|
|
|
|
|
## Providing locale rules to Angular
|
|
|
|
There are two approaches to providing locale rules to Angular:
|
|
|
|
### 1. Pre-bundled rule sets
|
|
|
|
You can pre-bundle the desired locale file with Angular by concatenating the content of the
|
|
locale-specific file to the end of `angular.js` or `angular.min.js` file.
|
|
|
|
For example on *nix, to create an angular.js file that contains localization rules for german
|
|
locale, you can do the following:
|
|
|
|
`cat angular.js i18n/angular-locale_de-de.js > angular_de-de.js`
|
|
|
|
When the application containing `angular_de-de.js` script instead of the generic angular.js script
|
|
starts, Angular is automatically pre-configured with localization rules for the german locale.
|
|
|
|
### 2. Including a locale script in `index.html`
|
|
|
|
You can also include the locale specific js file in the index.html page. For example, if one client
|
|
requires German locale, you would serve index_de-de.html which will look something like this:
|
|
|
|
```html
|
|
<html ng-app>
|
|
<head>
|
|
….
|
|
<script src="angular.js"></script>
|
|
<script src="i18n/angular-locale_de-de.js"></script>
|
|
….
|
|
</head>
|
|
</html>
|
|
```
|
|
|
|
### Comparison of the two approaches
|
|
|
|
Both approaches described above require you to prepare different `index.html` pages or JavaScript
|
|
files for each locale that your app may use. You also need to configure your server to serve
|
|
the correct file that correspond to the desired locale.
|
|
|
|
The second approach (including the locale JavaScript file in `index.html`) may be slower because
|
|
an extra script needs to be loaded.
|
|
|
|
|
|
## Caveats
|
|
|
|
Although Angular makes i18n convenient, there are several things you need to be conscious of as you
|
|
develop your app.
|
|
|
|
### Currency symbol
|
|
|
|
Angular's {@link ng.filter:currency currency filter} allows you to use the default currency symbol
|
|
from the {@link ng.$locale locale service}, or you can provide the filter with a custom currency
|
|
symbol.
|
|
|
|
<div class="alert alert-success">
|
|
**Best Practice:** If your app will be used only in one locale, it is fine to rely on the default
|
|
currency symbol. If you anticipate that viewers in other locales might use your app, you should
|
|
explicitly provide a currency symbol.
|
|
</div>
|
|
|
|
Let's say you are writing a banking app and you want to display an account balance of 1000 dollars.
|
|
You write the following binding using the currency filter:
|
|
|
|
```html
|
|
{{ 1000 | currency }}
|
|
```
|
|
|
|
If your app is currently in the `en-US` locale, the browser will show `$1000.00`. If someone in the
|
|
Japanese locale (`ja`) views your app, their browser will show a balance of `¥1000.00` instead.
|
|
This is problematic because $1000 is not the same as ¥1000.
|
|
|
|
In this case, you need to override the default currency symbol by providing the
|
|
{@link ng.filter:currency} currency filter with a currency symbol as a parameter.
|
|
|
|
If we change the above to `{{ 1000 | currency:"USD$"}}`, Angular will always show a balance of
|
|
`USD$1000` regardless of locale.
|
|
|
|
### Translation length
|
|
|
|
Translated strings/datetime formats can vary greatly in length. For example, `June 3, 1977` will be
|
|
translated to Spanish as `3 de junio de 1977`.
|
|
|
|
When internationalizing your app, you need to do thorough testing to make sure UI components behave
|
|
as expected even when their contents vary greatly in content size.
|
|
|
|
### Timezones
|
|
|
|
The Angular datetime filter uses the time zone settings of the browser. The same
|
|
application will show different time information depending on the time zone settings of the
|
|
computer that the application is running on. Neither JavaScript nor Angular currently supports
|
|
displaying the date with a timezone specified by the developer.
|
|
|
|
|
|
<a name="MessageFormat"></a>
|
|
## MessageFormat extensions
|
|
|
|
You can write localizable plural and gender based messages in Angular interpolation expressions and
|
|
`$interpolate` calls.
|
|
|
|
This syntax extension is provided by way of the `ngMessageFormat` module that your application can
|
|
depend upon (shipped separately as `angular-message-format.min.js` and `angular-message-format.js`.)
|
|
A current limitation of the `ngMessageFormat` module, is that it does not support redefining the
|
|
`$interpolate` start and end symbols. Only the default `{{` and `}}` are allowed.
|
|
|
|
The syntax extension is based on a subset of the ICU MessageFormat syntax that covers plurals and
|
|
gender selections. Please refer to the links in the “Further Reading” section at the bottom of this
|
|
section.
|
|
|
|
You may find it helpful to play with the following example as you read the explanations below:
|
|
|
|
<example name="message-format-example" module="messageFormatExample" deps="angular-message-format.js">
|
|
<file name="index.html">
|
|
<div ng-controller="ckCtrl">
|
|
<b>Set number of recipients</b>
|
|
<button ng-click="setNumRecipients(0)">None</button>
|
|
<button ng-click="setNumRecipients(1)">One</button>
|
|
<button ng-click="setNumRecipients(2)">Two</button>
|
|
<button ng-click="setNumRecipients(3)">Three</button>
|
|
|
|
|
|
<br><br>
|
|
<b>Sender's</b> name: <input ng-model="sender.name">
|
|
|
|
<br><br><b>Recipients</b><br>
|
|
<div ng-repeat="recipient in recipients">
|
|
Name: <input ng-model="recipient.name">
|
|
Gender: <button ng-click="setGender(recipient, 'male')">male</button>
|
|
<button ng-click="setGender(recipient, 'female')">female</button>
|
|
<button ng-click="setGender(recipient, 'other')">other</button>
|
|
</div>
|
|
|
|
<br><br><b>Message</b><br>
|
|
{{recipients.length, plural, offset:1
|
|
=0 {You ({{sender.name}}) gave no gifts}
|
|
=1 { {{ recipients[0].gender, select,
|
|
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) a gift.}
|
|
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) a gift.}
|
|
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) a gift.}
|
|
}}
|
|
}
|
|
one { {{ recipients[0].gender, select,
|
|
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) and one other person a gift.}
|
|
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) and one other person a gift.}
|
|
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) and one other person a gift.}
|
|
}}
|
|
}
|
|
other {You ({{sender.name}}) gave {{recipients.length}} people gifts. }
|
|
}}
|
|
|
|
<br><br><b>In an attribute</b><br>
|
|
<div attrib="{{recipients.length, plural, offset:1
|
|
=0 {You ({{sender.name}}) gave no gifts}
|
|
=1 { {{ recipients[0].gender, select,
|
|
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) a gift.}
|
|
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) a gift.}
|
|
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) a gift.}
|
|
}}
|
|
}
|
|
one { {{ recipients[0].gender, select,
|
|
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) and one other person a gift.}
|
|
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) and one other person a gift.}
|
|
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) and one other person a gift.}
|
|
}}
|
|
}
|
|
other {You ({{sender.name}}) gave {{recipients.length}} people gifts. }
|
|
}}">
|
|
This div has an attribute interpolated with messageformat. Use the DOM inspector to check it out.
|
|
</div>
|
|
</div>
|
|
</file>
|
|
<file name="app.js">
|
|
function Person(name, gender) {
|
|
this.name = name;
|
|
this.gender = gender;
|
|
}
|
|
|
|
angular.module('messageFormatExample', ['ngMessageFormat'])
|
|
.controller('ckCtrl', function($scope, $injector, $parse) {
|
|
var people = [new Person('Alice', 'female'),
|
|
new Person('Bob', 'male'),
|
|
new Person('Charlie', 'male')];
|
|
|
|
$scope.sender = new Person('Harry Potter', 'male');
|
|
$scope.recipients = people.slice();
|
|
|
|
$scope.setNumRecipients = function(n) {
|
|
n = n > people.length ? people.length : n;
|
|
$scope.recipients = people.slice(0, n);
|
|
};
|
|
|
|
$scope.setGender = function(person, gender) {
|
|
person.gender = gender;
|
|
};
|
|
});
|
|
</file>
|
|
</example>
|
|
|
|
### Plural Syntax
|
|
|
|
The syntax for plural based message selection looks like the following:
|
|
|
|
```text
|
|
{{NUMERIC_EXPRESSION, plural,
|
|
=0 {MESSAGE_WHEN_VALUE_IS_0}
|
|
=1 {MESSAGE_WHEN_VALUE_IS_1}
|
|
=2 {MESSAGE_WHEN_VALUE_IS_2}
|
|
=3 {MESSAGE_WHEN_VALUE_IS_3}
|
|
...
|
|
zero {MESSAGE_WHEN_PLURAL_CATEGORY_IS_ZERO}
|
|
one {MESSAGE_WHEN_PLURAL_CATEGORY_IS_ONE}
|
|
two {MESSAGE_WHEN_PLURAL_CATEGORY_IS_TWO}
|
|
few {MESSAGE_WHEN_PLURAL_CATEGORY_IS_FEW}
|
|
many {MESSAGE_WHEN_PLURAL_CATEGORY_IS_MANY}
|
|
other {MESSAGE_WHEN_THERE_IS_NO_MATCH}
|
|
}}
|
|
```
|
|
|
|
Please note that whitespace (including newline) is generally insignificant except as part of the
|
|
actual message text that occurs in curly braces. Whitespace is generally used to aid readability.
|
|
|
|
Here, `NUMERIC_EXPRESSION` is an expression that evaluates to a numeric value based on which the
|
|
displayed message should change based on pluralization rules.
|
|
|
|
Following the Angular expression, you would denote the plural extension syntax by the `, plural,`
|
|
syntax element. The spaces there are optional.
|
|
|
|
This is followed by a list of selection keyword and corresponding message pairs. The "other"
|
|
keyword and corresponding message are **required** but you may have as few or as many of the other
|
|
categories as you need.
|
|
|
|
#### Selection Keywords
|
|
|
|
The selection keywords can be either exact matches or language dependent [plural
|
|
categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html).
|
|
|
|
Exact matches are written as the equal sign followed by the exact value. `=0`, `=1`, `=2` and
|
|
`=123` are all examples of exact matches. Note that there should be no space between the equal sign
|
|
and the numeric value.
|
|
|
|
Plural category matches are single words corresponding to the [plural
|
|
categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) of
|
|
the CLDR plural category spec. These categories vary by locale. The "en" (English) locale, for
|
|
example, defines just "one" and "other" while the "ga" (Irish) locale defines "one", "two", "few",
|
|
"many" and "other". Typically, you would just write the categories for your language. During
|
|
translation, the translators will add or remove more categories depending on the target locale.
|
|
|
|
Exact matches always win over keyword matches. Therefore, if you define both `=0` and `zero`, when
|
|
the value of the expression is zero, the `=0` message is the one that will be selected. (The
|
|
duplicate keyword categories are helpful when used with the optional `offset` syntax described
|
|
later.)
|
|
|
|
|
|
#### Messages
|
|
|
|
Messages immediately follow a selection keyword and are optionally preceded by whitespace. They are
|
|
written in single curly braces (`{}`). They may contain Angular interpolation syntax inside them.
|
|
In addition, the `#` symbol is a placeholder for the actual numeric value of the expression.
|
|
|
|
### Simple plural example
|
|
|
|
```text
|
|
{{numMessages, plural,
|
|
=0 {You have no new messages}
|
|
=1 {You have one new message}
|
|
other {You have # new messages}
|
|
}}
|
|
```
|
|
|
|
Because these messages can themselves contain Angular expressions, you could also write this as
|
|
follows:
|
|
|
|
```text
|
|
{{numMessages, plural,
|
|
=0 {You have no new messages}
|
|
=1 {You have one new message}
|
|
other {You have {{numMessages}} new messages}
|
|
}}
|
|
```
|
|
|
|
|
|
### Plural syntax with optional `offset`
|
|
|
|
The plural syntax supports an optional `offset` syntax that is used in matching. It's simpler to
|
|
explain this with an example.
|
|
|
|
```text
|
|
{{recipients.length, plural, offset:1
|
|
=0 {You gave no gifts}
|
|
=1 {You gave {{recipients[0].name}} a gift}
|
|
one {You gave {{recipients[0].name}} and one other person a gift}
|
|
other {You gave {{recipients[0].name}} and # other people a gift}
|
|
}}
|
|
```
|
|
|
|
When an `offset` is specified, the matching works as follows. First, the exact value of the Angular
|
|
expression is matched against the exact matches (i.e. `=N` selectors) to find a match. If there is
|
|
one, that message is used. If there was no match, then the offset value is subtracted from the
|
|
value of the expression and locale specific pluralization rules are applied to this new value to
|
|
obtain its plural category (such as “one”, “few”, “many”, etc.) and a match is attempted against the
|
|
keyword selectors and the matching message is used. If there was no match, then the “other”
|
|
category (required) is used. The value of the `#` character inside a message is the value of
|
|
original expression reduced by the offset value that was specified.
|
|
|
|
### Escaping / Quoting
|
|
|
|
You will need to escape curly braces or the `#` character inside message texts if you want them to
|
|
be treated literally with no special meaning. You may quote/escape any character in your message
|
|
text by preceding it with a `\` (backslash) character. The backslash character removes any special
|
|
meaning to the character that immediately follows it. Therefore, you can escape or quote the
|
|
backslash itself by preceding it with another backslash character.
|
|
|
|
|
|
### Gender (aka select) Syntax
|
|
|
|
The gender support is provided by the more generic "select" syntax that is more akin to a switch
|
|
statement. It is general enough to support use for gender based messages.
|
|
|
|
The syntax for gender based message selection looks like the following:
|
|
|
|
```text
|
|
{{EXPRESSION, select,
|
|
male {MESSAGE_WHEN_EXPRESSION_IS_MALE}
|
|
female {MESSAGE_WHEN_EXPRESSION_IS_FEMALE}
|
|
...
|
|
other {MESSAGE_WHEN_THERE_IS_NO_GENDER_MATCH}
|
|
}}
|
|
```
|
|
|
|
Please note that whitespace (including newline) is generally insignificant except as part of the
|
|
actual message text that occurs in curly braces. Whitespace is generally used to aid readability.
|
|
|
|
Here, `EXPRESSION` is an Angular expression that evaluates to the gender of the person that
|
|
is used to select the message that should be displayed.
|
|
|
|
The Angular expression is followed by `, select,` where the spaces are optional.
|
|
|
|
This is followed by a list of selection keyword and corresponding message pairs. The "other"
|
|
keyword and corresponding message are **required** but you may have as few or as many of the other
|
|
gender values as you need (i.e. it isn't restricted to male/female.) Note however, that the
|
|
matching is **case-sensitive**.
|
|
|
|
#### Selection Keywords
|
|
|
|
Selection keywords are simple words like "male" and "female". The keyword, "other", and its
|
|
corresponding message are required while others are optional. It is used when the Angular
|
|
expression does not match (case-insensitively) any of the other keywords specified.
|
|
|
|
#### Messages
|
|
|
|
Messages immediately follow a selection keyword and are optionally preceded by whitespace. They are
|
|
written in single curly braces (`{}`). They may contain Angular interpolation syntax inside them.
|
|
|
|
### Simple gender example
|
|
|
|
```text
|
|
{{friendGender, select,
|
|
male {Invite him}
|
|
female {Invite her}
|
|
other {Invite them}
|
|
}}
|
|
```
|
|
|
|
### Nesting
|
|
|
|
As mentioned in the syntax for plural and select, the embedded messages can contain Angular
|
|
interpolation syntax. Since you can use MessageFormat extensions in Angular interpolation, this
|
|
allows you to nest plural and gender expressions in any order.
|
|
|
|
Please note that if these are intended to reach a translator and be translated, it is recommended
|
|
that the messages appear as a whole and not be split up.
|
|
|
|
### Demonstration of nesting
|
|
|
|
This is taken from the above example.
|
|
|
|
```text
|
|
{{recipients.length, plural, offset:1
|
|
=0 {You ({{sender.name}}) gave no gifts}
|
|
=1 { {{ recipients[0].gender, select,
|
|
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) a gift.}
|
|
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) a gift.}
|
|
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) a gift.}
|
|
}}
|
|
}
|
|
one { {{ recipients[0].gender, select,
|
|
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) and one other person a gift.}
|
|
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) and one other person a gift.}
|
|
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) and one other person a gift.}
|
|
}}
|
|
}
|
|
other {You ({{sender.name}}) gave {{recipients.length}} people gifts. }
|
|
}}
|
|
```
|
|
|
|
### Differences from the ICU MessageFormat syntax
|
|
|
|
This section is useful to you if you're already familiar with the ICU MessageFormat syntax.
|
|
|
|
This syntax extension, while based on MessageFormat, has been designed to be backwards compatible
|
|
with existing AngularJS interpolation expressions. The key rule is simply this: **All
|
|
interpolations are done inside double curlies.** The top level comma operator after an expression
|
|
inside the double curlies causes MessageFormat extensions to be recognized. Such a top level comma
|
|
is otherwise illegal in an Angular expression and is used by MessageFormat to specify the function
|
|
(such as plural/select) and it's related syntax.
|
|
|
|
To understand the extension, take a look at the ICU MessageFormat syntax as specified by the ICU
|
|
documentation. Anywhere in that MessageFormat that you have regular message text and you want to
|
|
substitute an expression, just put it in double curlies instead of single curlies that MessageFormat
|
|
dictates. This has a huge advantage. **You are no longer limited to simple identifiers for
|
|
substitutions**. Because you are using double curlies, you can stick in any arbitrary interpolation
|
|
syntax there, including nesting more MessageFormat expressions!
|
|
|
|
### Further Reading
|
|
For more details, please refer to our [design doc](https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit).
|
|
You can read more about the ICU MessageFormat syntax at
|
|
[Formatting Messages | ICU User Guide](http://userguide.icu-project.org/formatparse/messages#TOC-MessageFormat).
|