Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i18n - support global locale data #33523

Closed

Conversation

petebacondarwin
Copy link
Member

@petebacondarwin petebacondarwin commented Oct 31, 2019

This is my preferred solution to generating (and using) locales via the global object.

See https://hackmd.io/BFc4P-JYT7-VO0id7-afhg

It is best to review commit by commit, since one simply adds over 500 newly generated locale files.

The idea is that at translation time (after the build) the required locale data is included in what will be executed by the browser.

  • In the CLI
    • The appropriate locale data file will be pre-prended as-is to the main.js bundle at translation time (after the main build).
  • Outside of CLI:
    • The developer should place a <script src="locale.js"> tag in the index.html before the main.js script tag
    • The translation tool should be configured to replace that file with the appropriate locale data file.

Executing this script will attach the locale data to the global scope as ng.common.locale['xx-xx'] making it available to the application.

At runtime, when Angular requires locale data, the findLocaleData() function is called. This function returns locales that have been explicitly registered, the same as before v9, by a call to registerLocaleData() but now it will also fallback to looking for the locale data on the global scope, ng.common.locale[..].


The previous PR #33504 simply updated the current locale files, which are UMD, to support being attached to global scope. This is not optimal for a number of reasons:

  • The locale data lookup is based on a normalized form of the locale name, which is not included in the UMD files. This meant that the global lookup had to do an O(n) search of the global locales to match the normalized locale name.
  • The UMD locale files contain a large amount of boilerplate to handle the different usage scenarios. Also the current locale files are split into "basic" and "extra" bundles, each containing the boilerplate. Since the locale scripts must be applied during translation, after the build has completed, there is little chance to compact these files and so the resulting bundle sizes (e.g. of main.js) are increased significantly.

This PR takes a different approach. The locale data files are generated from CLDR data on a periodic basis. Therefore it is straightforward to generate additional locale files that are optimised for the global attachment use case that is required here. These new files have the following properties:

  • Each "global" locale data file contains both the "basic" and "extra" data in a single file, pre-arranged in the format that findLocaleData() requires.
  • The files do not contain any general UMD boilerplate. They contain a simple IIFE that attaches the locale data to the appropriate global scope.
  • The name of the property on the global scope is the "normalized" locale name, making it trivial to retrieve the locale data in the findLocaleData() function.

@petebacondarwin petebacondarwin changed the title Locale global umd 2 Locale global 2 Oct 31, 2019
root.ng = root.ng || {};
root.ng.common = root.ng.common || {};
root.ng.common.locale = root.ng.common.locale || {};
const u = undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fairly certain that the compression is better if you don't capture undefined

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is a bit of strange one. It was originally added here #23265.
Not 100% sure behind the reasoning for using a constant u.
Perhaps @ocombe can explain?

Copy link
Contributor

@ocombe ocombe Nov 1, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So initially it was because we removed the undefined values in the arrays (because they are not necessary): [1, , ,3] is valid, and the files were smaller
But then some people complained that it was causing errors in some webpack plugins, so we were forced to re-add the undefined values
We did some comparisons and since all the files are independent (they are not always bundled, some people lazy load them), it was smaller to just define a constant equal to undefined.
Since then the compilers might have gotten smarter, maybe it isn't necessary anymore, you can try and benchmark that :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ocombe for the explanation. Data is king! We should indeed measure it.

As it turns out this PR is generating JS files that will not be further processed, so I am thinking that the generation of those files should go through terser to get them as compact as possible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for just inlining undefined and not trying to be compressor.
👍 for going through terser

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to update BUILD.bazel to pass these generated files through terser but it turned out to be a lot more complicated that expected. I suggest we come back to that optimisation task after this PR has landed.

@petebacondarwin petebacondarwin added area: build & ci Related the build and CI infrastructure of the project area: common Issues related to APIs in the @angular/common package area: core Issues related to the framework runtime area: i18n target: patch This PR is targeted for the next patch release labels Nov 1, 2019
@ngbot ngbot bot modified the milestone: needsTriage Nov 1, 2019
@ocombe
Copy link
Contributor

ocombe commented Nov 1, 2019 via email

@angular angular deleted a comment from petebacondarwin Nov 1, 2019
@petebacondarwin
Copy link
Member Author

petebacondarwin commented Nov 1, 2019

@ocombe these files are pretty obscure even when they are not minified :-)
Note that I am not suggesting that we minify the .ts files.

@petebacondarwin petebacondarwin force-pushed the locale-global-umd-2 branch 2 times, most recently from c13b677 to 321b6a8 Compare November 1, 2019 10:09
@petebacondarwin petebacondarwin marked this pull request as ready for review November 1, 2019 11:39
@petebacondarwin petebacondarwin requested review from a team as code owners November 1, 2019 11:39
@petebacondarwin petebacondarwin added the action: review The PR is still awaiting reviews from at least one requested reviewer label Nov 1, 2019
@petebacondarwin petebacondarwin changed the title Locale global 2 i18n - support global locale data Nov 1, 2019
@clydin
Copy link
Member

clydin commented Nov 1, 2019

This appears to address all the concerns I had. I’ll give it a more thorough review today but nice job putting this together.

Copy link
Contributor

@mhevery mhevery left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comments, LGTM otherwise.

tools/gulp-tasks/cldr/extract.js Show resolved Hide resolved
tools/gulp-tasks/cldr/extract.js Outdated Show resolved Hide resolved
`${I18N_CORE_FOLDER}/locale_en.ts`
],
{base: '.'})
.pipe(format.format('file', clangFormat))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that you are doing clang-format here, but I don't see the output file actually being formatted. Something is going on here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally I had not planned to format the generated files.
Since we have agreed to pass these generated files through terser before adding to the npm package, I changed the PR to actually format the new files with clang after generation.

tools/gulp-tasks/cldr/extract.js Show resolved Hide resolved
tools/gulp-tasks/cldr/extract.js Show resolved Hide resolved
packages/common/test/pipes/date_pipe_spec.ts Show resolved Hide resolved
packages/core/src/i18n/locale_data_api.ts Outdated Show resolved Hide resolved
packages/core/test/i18n/locale_data_api_spec.ts Outdated Show resolved Hide resolved
packages/core/src/i18n/locale_data_api.ts Outdated Show resolved Hide resolved
packages/core/src/i18n/locale_data_api.ts Outdated Show resolved Hide resolved
packages/common/locales/BUILD.bazel Show resolved Hide resolved
@mhevery
Copy link
Contributor

mhevery commented Nov 4, 2019

presubmit

@petebacondarwin petebacondarwin added action: merge The PR is ready for merge by the caretaker and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels Nov 5, 2019
@atscott atscott closed this in 502dd89 Nov 5, 2019
atscott pushed a commit that referenced this pull request Nov 5, 2019
In order to support adding locales during compile-time
inlining of translations (i.e. after the TS build has completed),
we need to be able to attach the locale to the global scope.

This commit modifies CLDR extraction to emit additional "global"
locale files that appear in the `@angular/common/locales/global` folder.

These files are of the form:

```
(function() {
  const root = typeof globalThis !== 'undefined' && globalThis ||
      typeof global !== 'undefined' && global || typeof window !== 'undefined' && window;
  root.ng = root.ng || {};
  root.ng.common = root.ng.common || {};
  root.ng.common.locale = root.ng.common.locale || {};
  const u = undefined;
  function plural(n) {
    if (n === 1) return 1;
    return 5;
  }
  root.ng.common.locale['xx-yy'] = [...];
})();
```

The IIFE will ensure that `ng.common.locale` exists and attach the
given locale (and its "extras") to it using it "normalized" locale
name.

* "extras": in the UMD module locale files the "extra" locale data,
currently the day period rules, and extended day period data, are
stored in separate files under the "common/locales/extra" folder.

* "normalized": Angular references locales using a normalized form,
which is lower case with `_` replaced by `-`. For example:
`en_UK` => `en-uk`.

PR Close #33523
atscott pushed a commit that referenced this pull request Nov 5, 2019
This commit contains the global locale files generated by the change to
the CLDR `extract.js` tool, from the previous commit.

PR Close #33523
atscott pushed a commit that referenced this pull request Nov 5, 2019
To limit the exposure of the private `LOCALE_DATA` from outside
`@angular/core` this commit exposes private functions in the core
to hide the internal structures better.

* The `registerLocaleData()` implementation has moved from
`@angular/common` to `@angular/core`. A stub that delegates to
core has been left in common for backward compatibility.
* A new `ɵunregisterLocaleData()` function has been provided,
which is particularly useful in tests to clear out registered locales
to prevent subsequent tests from being affected.
* A private export of `ɵregisterLocaleData()` has been removed
from `@angular/common`. This was not being used and is accessible
via `@angular/core` anyway.

PR Close #33523
atscott pushed a commit that referenced this pull request Nov 5, 2019
To support compile time localization, we need to be
able to provide the locales via a well known global property.

This commit changes `getLocaleData()` so that it will attempt
to read the local from the global `ng.common.locales` if the
locale has not already been registered via `registerLocaleData()`.

PR Close #33523
atscott pushed a commit that referenced this pull request Nov 5, 2019
atscott pushed a commit that referenced this pull request Nov 5, 2019
In order to support adding locales during compile-time
inlining of translations (i.e. after the TS build has completed),
we need to be able to attach the locale to the global scope.

This commit modifies CLDR extraction to emit additional "global"
locale files that appear in the `@angular/common/locales/global` folder.

These files are of the form:

```
(function() {
  const root = typeof globalThis !== 'undefined' && globalThis ||
      typeof global !== 'undefined' && global || typeof window !== 'undefined' && window;
  root.ng = root.ng || {};
  root.ng.common = root.ng.common || {};
  root.ng.common.locale = root.ng.common.locale || {};
  const u = undefined;
  function plural(n) {
    if (n === 1) return 1;
    return 5;
  }
  root.ng.common.locale['xx-yy'] = [...];
})();
```

The IIFE will ensure that `ng.common.locale` exists and attach the
given locale (and its "extras") to it using it "normalized" locale
name.

* "extras": in the UMD module locale files the "extra" locale data,
currently the day period rules, and extended day period data, are
stored in separate files under the "common/locales/extra" folder.

* "normalized": Angular references locales using a normalized form,
which is lower case with `_` replaced by `-`. For example:
`en_UK` => `en-uk`.

PR Close #33523
atscott pushed a commit that referenced this pull request Nov 5, 2019
This commit contains the global locale files generated by the change to
the CLDR `extract.js` tool, from the previous commit.

PR Close #33523
atscott pushed a commit that referenced this pull request Nov 5, 2019
To limit the exposure of the private `LOCALE_DATA` from outside
`@angular/core` this commit exposes private functions in the core
to hide the internal structures better.

* The `registerLocaleData()` implementation has moved from
`@angular/common` to `@angular/core`. A stub that delegates to
core has been left in common for backward compatibility.
* A new `ɵunregisterLocaleData()` function has been provided,
which is particularly useful in tests to clear out registered locales
to prevent subsequent tests from being affected.
* A private export of `ɵregisterLocaleData()` has been removed
from `@angular/common`. This was not being used and is accessible
via `@angular/core` anyway.

PR Close #33523
atscott pushed a commit that referenced this pull request Nov 5, 2019
To support compile time localization, we need to be
able to provide the locales via a well known global property.

This commit changes `getLocaleData()` so that it will attempt
to read the local from the global `ng.common.locales` if the
locale has not already been registered via `registerLocaleData()`.

PR Close #33523
@petebacondarwin petebacondarwin deleted the locale-global-umd-2 branch November 6, 2019 09:06
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
…#33523)

In order to support adding locales during compile-time
inlining of translations (i.e. after the TS build has completed),
we need to be able to attach the locale to the global scope.

This commit modifies CLDR extraction to emit additional "global"
locale files that appear in the `@angular/common/locales/global` folder.

These files are of the form:

```
(function() {
  const root = typeof globalThis !== 'undefined' && globalThis ||
      typeof global !== 'undefined' && global || typeof window !== 'undefined' && window;
  root.ng = root.ng || {};
  root.ng.common = root.ng.common || {};
  root.ng.common.locale = root.ng.common.locale || {};
  const u = undefined;
  function plural(n) {
    if (n === 1) return 1;
    return 5;
  }
  root.ng.common.locale['xx-yy'] = [...];
})();
```

The IIFE will ensure that `ng.common.locale` exists and attach the
given locale (and its "extras") to it using it "normalized" locale
name.

* "extras": in the UMD module locale files the "extra" locale data,
currently the day period rules, and extended day period data, are
stored in separate files under the "common/locales/extra" folder.

* "normalized": Angular references locales using a normalized form,
which is lower case with `_` replaced by `-`. For example:
`en_UK` => `en-uk`.

PR Close angular#33523
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
This commit contains the global locale files generated by the change to
the CLDR `extract.js` tool, from the previous commit.

PR Close angular#33523
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
…lar#33523)

To limit the exposure of the private `LOCALE_DATA` from outside
`@angular/core` this commit exposes private functions in the core
to hide the internal structures better.

* The `registerLocaleData()` implementation has moved from
`@angular/common` to `@angular/core`. A stub that delegates to
core has been left in common for backward compatibility.
* A new `ɵunregisterLocaleData()` function has been provided,
which is particularly useful in tests to clear out registered locales
to prevent subsequent tests from being affected.
* A private export of `ɵregisterLocaleData()` has been removed
from `@angular/common`. This was not being used and is accessible
via `@angular/core` anyway.

PR Close angular#33523
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
To support compile time localization, we need to be
able to provide the locales via a well known global property.

This commit changes `getLocaleData()` so that it will attempt
to read the local from the global `ng.common.locales` if the
locale has not already been registered via `registerLocaleData()`.

PR Close angular#33523
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
…#33523)

In order to support adding locales during compile-time
inlining of translations (i.e. after the TS build has completed),
we need to be able to attach the locale to the global scope.

This commit modifies CLDR extraction to emit additional "global"
locale files that appear in the `@angular/common/locales/global` folder.

These files are of the form:

```
(function() {
  const root = typeof globalThis !== 'undefined' && globalThis ||
      typeof global !== 'undefined' && global || typeof window !== 'undefined' && window;
  root.ng = root.ng || {};
  root.ng.common = root.ng.common || {};
  root.ng.common.locale = root.ng.common.locale || {};
  const u = undefined;
  function plural(n) {
    if (n === 1) return 1;
    return 5;
  }
  root.ng.common.locale['xx-yy'] = [...];
})();
```

The IIFE will ensure that `ng.common.locale` exists and attach the
given locale (and its "extras") to it using it "normalized" locale
name.

* "extras": in the UMD module locale files the "extra" locale data,
currently the day period rules, and extended day period data, are
stored in separate files under the "common/locales/extra" folder.

* "normalized": Angular references locales using a normalized form,
which is lower case with `_` replaced by `-`. For example:
`en_UK` => `en-uk`.

PR Close angular#33523
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
This commit contains the global locale files generated by the change to
the CLDR `extract.js` tool, from the previous commit.

PR Close angular#33523
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
…lar#33523)

To limit the exposure of the private `LOCALE_DATA` from outside
`@angular/core` this commit exposes private functions in the core
to hide the internal structures better.

* The `registerLocaleData()` implementation has moved from
`@angular/common` to `@angular/core`. A stub that delegates to
core has been left in common for backward compatibility.
* A new `ɵunregisterLocaleData()` function has been provided,
which is particularly useful in tests to clear out registered locales
to prevent subsequent tests from being affected.
* A private export of `ɵregisterLocaleData()` has been removed
from `@angular/common`. This was not being used and is accessible
via `@angular/core` anyway.

PR Close angular#33523
mohaxspb pushed a commit to mohaxspb/angular that referenced this pull request Nov 7, 2019
To support compile time localization, we need to be
able to provide the locales via a well known global property.

This commit changes `getLocaleData()` so that it will attempt
to read the local from the global `ng.common.locales` if the
locale has not already been registered via `registerLocaleData()`.

PR Close angular#33523
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Dec 7, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: build & ci Related the build and CI infrastructure of the project area: common Issues related to APIs in the @angular/common package area: core Issues related to the framework runtime area: i18n cla: yes target: patch This PR is targeted for the next patch release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants