diff --git a/integration/cli-hello-world-ivy-i18n/src/polyfills.ts b/integration/cli-hello-world-ivy-i18n/src/polyfills.ts
index ec4e202bec..93bcf8a403 100644
--- a/integration/cli-hello-world-ivy-i18n/src/polyfills.ts
+++ b/integration/cli-hello-world-ivy-i18n/src/polyfills.ts
@@ -84,7 +84,7 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* Load `$localize` onto the global scope - used if i18n tags appear in Angular templates.
*/
-import '@angular/localize';
+import '@angular/localize/init';
/***************************************************************************************************
* APPLICATION IMPORTS
diff --git a/integration/side-effects/snapshots/core/esm2015.js b/integration/side-effects/snapshots/core/esm2015.js
index 3c9b0a8fee..4a4b8bf84e 100644
--- a/integration/side-effects/snapshots/core/esm2015.js
+++ b/integration/side-effects/snapshots/core/esm2015.js
@@ -13,5 +13,5 @@ const __global = "undefined" !== typeof global && global;
const _global = __globalThis || __global || __window || __self;
if (ngDevMode) _global.$localize = _global.$localize || function() {
- throw new Error("It looks like your application or one of its dependencies is using i18n.\n" + "Angular 9 introduced a global `$localize()` function that needs to be loaded.\n" + "Please add `import '@angular/localize';` to your polyfills.ts file.");
+ throw new Error("It looks like your application or one of its dependencies is using i18n.\n" + "Angular 9 introduced a global `$localize()` function that needs to be loaded.\n" + "Please add `import '@angular/localize/init';` to your polyfills.ts file.");
};
diff --git a/integration/side-effects/snapshots/core/esm5.js b/integration/side-effects/snapshots/core/esm5.js
index ba58f35bd7..e969f1091b 100644
--- a/integration/side-effects/snapshots/core/esm5.js
+++ b/integration/side-effects/snapshots/core/esm5.js
@@ -15,5 +15,5 @@ var __global = "undefined" !== typeof global && global;
var _global = __globalThis || __global || __window || __self;
if (ngDevMode) _global.$localize = _global.$localize || function() {
- throw new Error("It looks like your application or one of its dependencies is using i18n.\n" + "Angular 9 introduced a global `$localize()` function that needs to be loaded.\n" + "Please add `import '@angular/localize';` to your polyfills.ts file.");
+ throw new Error("It looks like your application or one of its dependencies is using i18n.\n" + "Angular 9 introduced a global `$localize()` function that needs to be loaded.\n" + "Please add `import '@angular/localize/init';` to your polyfills.ts file.");
};
diff --git a/modules/benchmarks/src/expanding_rows/BUILD.bazel b/modules/benchmarks/src/expanding_rows/BUILD.bazel
index 9b1f32f75f..9360501f9b 100644
--- a/modules/benchmarks/src/expanding_rows/BUILD.bazel
+++ b/modules/benchmarks/src/expanding_rows/BUILD.bazel
@@ -14,7 +14,7 @@ ng_module(
"//packages:types",
"//packages/common",
"//packages/core",
- "//packages/localize",
+ "//packages/localize/init",
"//packages/platform-browser",
"@npm//rxjs",
],
diff --git a/modules/benchmarks/src/expanding_rows/index_aot.ts b/modules/benchmarks/src/expanding_rows/index_aot.ts
index f46ffce318..4ba9a19a1e 100644
--- a/modules/benchmarks/src/expanding_rows/index_aot.ts
+++ b/modules/benchmarks/src/expanding_rows/index_aot.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
// This benchmark uses i18n in its `ExpandingRowSummary` component so `$localize` must be loaded.
-import '@angular/localize';
+import '@angular/localize/init';
import {enableProdMode} from '@angular/core';
import {platformBrowser} from '@angular/platform-browser';
diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts
index 4b66d5f5f3..11511b64ce 100644
--- a/packages/core/src/core.ts
+++ b/packages/core/src/core.ts
@@ -47,6 +47,6 @@ if (ngDevMode) {
throw new Error(
'It looks like your application or one of its dependencies is using i18n.\n' +
'Angular 9 introduced a global `$localize()` function that needs to be loaded.\n' +
- 'Please add `import \'@angular/localize\';` to your polyfills.ts file.');
+ 'Please add `import \'@angular/localize/init\';` to your polyfills.ts file.');
};
}
diff --git a/packages/core/src/render3/i18n.md b/packages/core/src/render3/i18n.md
index f079fe2a22..f0469261a9 100644
--- a/packages/core/src/render3/i18n.md
+++ b/packages/core/src/render3/i18n.md
@@ -1052,7 +1052,7 @@ The generated code needs work with:
The solution is to take advantage of compile time constants (e.g. `CLOSURE`) like so:
```typescript
-import '@angular/localize';
+import '@angular/localize/init';
let MSG_hello;
if (CLOSURE) {
diff --git a/packages/core/test/BUILD.bazel b/packages/core/test/BUILD.bazel
index 52f0a455c1..a249241bb6 100644
--- a/packages/core/test/BUILD.bazel
+++ b/packages/core/test/BUILD.bazel
@@ -27,7 +27,7 @@ ts_library(
"//packages/core/src/reflection",
"//packages/core/src/util",
"//packages/core/testing",
- "//packages/localize",
+ "//packages/localize/init",
"//packages/platform-browser",
"//packages/platform-browser-dynamic",
"//packages/platform-browser/animations",
diff --git a/packages/core/test/acceptance/BUILD.bazel b/packages/core/test/acceptance/BUILD.bazel
index db2a0e28be..862c5eb34b 100644
--- a/packages/core/test/acceptance/BUILD.bazel
+++ b/packages/core/test/acceptance/BUILD.bazel
@@ -20,7 +20,7 @@ ts_library(
"//packages/core/src/util",
"//packages/core/testing",
"//packages/localize",
- "//packages/localize/run_time",
+ "//packages/localize/init",
"//packages/platform-browser",
"//packages/platform-browser-dynamic",
"//packages/platform-browser/animations",
diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts
index 41dca47aea..ac77ee1dde 100644
--- a/packages/core/test/acceptance/i18n_spec.ts
+++ b/packages/core/test/acceptance/i18n_spec.ts
@@ -5,13 +5,15 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-import '@angular/localize';
+// Make the `$localize()` global function available to the compiled templates, and the direct calls
+// below. This would normally be done inside the application `polyfills.ts` file.
+import '@angular/localize/init';
import {registerLocaleData} from '@angular/common';
import localeRo from '@angular/common/locales/ro';
import {Component, ContentChild, ContentChildren, Directive, HostBinding, Input, LOCALE_ID, QueryList, TemplateRef, Type, ViewChild, ViewContainerRef, Pipe, PipeTransform} from '@angular/core';
import {setDelayProjection} from '@angular/core/src/render3/instructions/projection';
import {TestBed} from '@angular/core/testing';
-import {loadTranslations} from '@angular/localize/run_time';
+import {loadTranslations} from '@angular/localize';
import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {onlyInIvy} from '@angular/private/testing';
diff --git a/packages/core/test/acceptance/view_container_ref_spec.ts b/packages/core/test/acceptance/view_container_ref_spec.ts
index efa28c70b0..80d7231b1d 100644
--- a/packages/core/test/acceptance/view_container_ref_spec.ts
+++ b/packages/core/test/acceptance/view_container_ref_spec.ts
@@ -11,7 +11,7 @@ import {Compiler, Component, ComponentFactoryResolver, Directive, DoCheck, Eleme
import {Input} from '@angular/core/src/metadata';
import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode';
import {TestBed, TestComponentRenderer} from '@angular/core/testing';
-import {loadTranslations} from '@angular/localize/run_time';
+import {loadTranslations} from '@angular/localize';
import {By, DomSanitizer} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
diff --git a/packages/core/test/bundling/hello_world_i18n/BUILD.bazel b/packages/core/test/bundling/hello_world_i18n/BUILD.bazel
index acd83c5928..83d0657776 100644
--- a/packages/core/test/bundling/hello_world_i18n/BUILD.bazel
+++ b/packages/core/test/bundling/hello_world_i18n/BUILD.bazel
@@ -11,7 +11,8 @@ ng_module(
],
deps = [
"//packages/core",
- "//packages/localize/run_time",
+ "//packages/localize",
+ "//packages/localize/init",
],
)
diff --git a/packages/core/test/bundling/hello_world_i18n/translations.ts b/packages/core/test/bundling/hello_world_i18n/translations.ts
index 7d1fb21f54..7ffe25a657 100644
--- a/packages/core/test/bundling/hello_world_i18n/translations.ts
+++ b/packages/core/test/bundling/hello_world_i18n/translations.ts
@@ -5,8 +5,10 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-
-import {loadTranslations} from '@angular/localize/run_time';
+// Make the `$localize()` global function available to the compiled templates, and the direct calls
+// below. This would normally be done inside the application `polyfills.ts` file.
+import '@angular/localize/init';
+import {loadTranslations} from '@angular/localize';
const translations = {
'Hello World!': 'Bonjour Monde!',
diff --git a/packages/core/test/bundling/todo_i18n/BUILD.bazel b/packages/core/test/bundling/todo_i18n/BUILD.bazel
index 93dc3994b2..628e9849a3 100644
--- a/packages/core/test/bundling/todo_i18n/BUILD.bazel
+++ b/packages/core/test/bundling/todo_i18n/BUILD.bazel
@@ -17,7 +17,8 @@ ng_module(
"//packages/common",
"//packages/core",
"//packages/core/test/bundling/util:reflect_metadata",
- "//packages/localize/run_time",
+ "//packages/localize",
+ "//packages/localize/init",
],
)
diff --git a/packages/core/test/bundling/todo_i18n/index.ts b/packages/core/test/bundling/todo_i18n/index.ts
index 609c372562..648da7d67f 100644
--- a/packages/core/test/bundling/todo_i18n/index.ts
+++ b/packages/core/test/bundling/todo_i18n/index.ts
@@ -6,9 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
import '@angular/core/test/bundling/util/src/reflect_metadata';
-// Make the `$localize()` global function available to the compiled templates, and the direct calls
-// below. This would normally be done inside the application `polyfills.ts` file.
-import '@angular/localize';
import './translations';
import {CommonModule} from '@angular/common';
import {Component, Injectable, NgModule, ViewEncapsulation, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core';
diff --git a/packages/core/test/bundling/todo_i18n/todo_e2e_spec.ts b/packages/core/test/bundling/todo_i18n/todo_e2e_spec.ts
index 96adcdc609..74d560ab0e 100644
--- a/packages/core/test/bundling/todo_i18n/todo_e2e_spec.ts
+++ b/packages/core/test/bundling/todo_i18n/todo_e2e_spec.ts
@@ -5,10 +5,12 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-
+import '@angular/localize/init';
import '@angular/compiler';
+
import {ɵwhenRendered as whenRendered} from '@angular/core';
import {getComponent} from '@angular/core/src/render3';
+import {clearTranslations} from '@angular/localize';
import {withBody} from '@angular/private/testing';
import * as path from 'path';
@@ -19,12 +21,7 @@ describe('functional test for todo i18n', () => {
BUNDLES.forEach(bundle => {
describe(bundle, () => {
it('should render todo i18n', withBody('', async() => {
- // We need to delete the dummy `$localize` that was added because of the import of
- // `@angular/core` at the top of this file.
- // Also to clear out the translations from the previous test.
- // This would not be needed in normal applications since the import of
- // `@angular/localize` would be in polyfill.ts before any other import.
- ($localize as any) = undefined;
+ clearTranslations();
require(path.join(PACKAGE, bundle));
const toDoAppComponent = getComponent(document.querySelector('todo-app') !);
expect(document.body.textContent).toContain('liste de tâches');
diff --git a/packages/core/test/bundling/todo_i18n/translations.ts b/packages/core/test/bundling/todo_i18n/translations.ts
index f0871aa370..370bad234a 100644
--- a/packages/core/test/bundling/todo_i18n/translations.ts
+++ b/packages/core/test/bundling/todo_i18n/translations.ts
@@ -5,8 +5,10 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-
-import {loadTranslations} from '@angular/localize/run_time';
+// Make the `$localize()` global function available to the compiled templates, and the direct calls
+// below. This would normally be done inside the application `polyfills.ts` file.
+import '@angular/localize/init';
+import {loadTranslations} from '@angular/localize';
export const translations = {
'What needs to be done?': `Qu'y a-t-il à faire ?`,
diff --git a/packages/core/test/linker/ng_container_integration_spec.ts b/packages/core/test/linker/ng_container_integration_spec.ts
index 70fbdb91c3..6f7fca54a2 100644
--- a/packages/core/test/linker/ng_container_integration_spec.ts
+++ b/packages/core/test/linker/ng_container_integration_spec.ts
@@ -5,7 +5,9 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-import '@angular/localize';
+// Make the `$localize()` global function available to the compiled templates, and the direct calls
+// below. This would normally be done inside the application `polyfills.ts` file.
+import '@angular/localize/init';
import {AfterContentInit, AfterViewInit, Component, ContentChildren, Directive, Input, QueryList, ViewChildren, ɵivyEnabled as ivyEnabled} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {isCommentNode} from '@angular/platform-browser/testing/src/browser_util';
diff --git a/packages/localize/BUILD.bazel b/packages/localize/BUILD.bazel
index c27a8aa673..b05ebec99e 100644
--- a/packages/localize/BUILD.bazel
+++ b/packages/localize/BUILD.bazel
@@ -12,6 +12,7 @@ ts_library(
),
module_name = "@angular/localize",
deps = [
+ "//packages/localize/src/localize",
"@npm//@types/node",
],
)
@@ -20,7 +21,7 @@ ng_package(
name = "npm_package",
srcs = [
"package.json",
- "//packages/localize/run_time:package.json",
+ "//packages/localize/init:package.json",
],
entry_point = ":index.ts",
tags = [
@@ -28,6 +29,6 @@ ng_package(
],
deps = [
":localize",
- "//packages/localize/run_time",
+ "//packages/localize/init",
],
)
diff --git a/packages/localize/index.ts b/packages/localize/index.ts
index 6b76f3b6a8..1f5ed0ec2c 100644
--- a/packages/localize/index.ts
+++ b/packages/localize/index.ts
@@ -5,73 +5,9 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-import {_global} from './src/global';
-import {$localize as _localize, LocalizeFn, TranslateFn} from './src/localize';
-// Attach $localize to the global context, as a side-effect of this module.
-_global.$localize = _localize;
+// DO NOT ADD public exports to this file.
+// The public API exports are specified in the `./localize` module, which is checked by the
+// public_api_guard rules
-export {LocalizeFn, TranslateFn};
-
-// `declare global` allows us to escape the current module and place types on the global namespace
-declare global {
- /**
- * Tag a template literal string for localization.
- *
- * For example:
- *
- * ```ts
- * $localize `some string to localize`
- * ```
- *
- * **Naming placeholders**
- *
- * If the template literal string contains expressions then you can optionally name the
- * placeholder
- * associated with each expression. Do this by providing the placeholder name wrapped in `:`
- * characters directly after the expression. These placeholder names are stripped out of the
- * rendered localized string.
- *
- * For example, to name the `item.length` expression placeholder `itemCount` you write:
- *
- * ```ts
- * $localize `There are ${item.length}:itemCount: items`;
- * ```
- *
- * If you need to use a `:` character directly an expression you must either provide a name or you
- * can escape the `:` by preceding it with a backslash:
- *
- * For example:
- *
- * ```ts
- * $localize `${label}:label:: ${}`
- * // or
- * $localize `${label}\: ${}`
- * ```
- *
- * **Processing localized strings:**
- *
- * There are three scenarios:
- *
- * * **compile-time inlining**: the `$localize` tag is transformed at compile time by a
- * transpiler,
- * removing the tag and replacing the template literal string with a translated literal string
- * from a collection of translations provided to the transpilation tool.
- *
- * * **run-time evaluation**: the `$localize` tag is a run-time function that replaces and
- * reorders
- * the parts (static strings and expressions) of the template literal string with strings from a
- * collection of translations loaded at run-time.
- *
- * * **pass-through evaluation**: the `$localize` tag is a run-time function that simply evaluates
- * the original template literal string without applying any translations to the parts. This
- * version
- * is used during development or where there is no need to translate the localized template
- * literals.
- *
- * @param messageParts a collection of the static parts of the template string.
- * @param expressions a collection of the values of each placeholder in the template string.
- * @returns the translated string, with the `messageParts` and `expressions` interleaved together.
- */
- const $localize: LocalizeFn;
-}
+export * from './localize';
\ No newline at end of file
diff --git a/packages/localize/run_time/BUILD.bazel b/packages/localize/init/BUILD.bazel
similarity index 71%
rename from packages/localize/run_time/BUILD.bazel
rename to packages/localize/init/BUILD.bazel
index 0fedff66bf..0ffd78b265 100644
--- a/packages/localize/run_time/BUILD.bazel
+++ b/packages/localize/init/BUILD.bazel
@@ -5,15 +5,15 @@ package(default_visibility = ["//visibility:public"])
exports_files(["package.json"])
ts_library(
- name = "run_time",
+ name = "init",
srcs = glob(
[
"**/*.ts",
],
),
- module_name = "@angular/localize/run_time",
+ module_name = "@angular/localize/init",
deps = [
- "//packages/localize",
+ "//packages/localize/src/localize",
"@npm//@types/node",
],
)
diff --git a/packages/localize/init/index.ts b/packages/localize/init/index.ts
new file mode 100644
index 0000000000..610b327657
--- /dev/null
+++ b/packages/localize/init/index.ts
@@ -0,0 +1,76 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {$localize, LocalizeFn, _global} from '../src/localize';
+
+export {LocalizeFn, TranslateFn} from '../src/localize';
+
+// Attach $localize to the global context, as a side-effect of this module.
+_global.$localize = $localize;
+
+// `declare global` allows us to escape the current module and place types on the global namespace
+declare global {
+ /**
+ * Tag a template literal string for localization.
+ *
+ * For example:
+ *
+ * ```ts
+ * $localize `some string to localize`
+ * ```
+ *
+ * **Naming placeholders**
+ *
+ * If the template literal string contains expressions then you can optionally name the
+ * placeholder
+ * associated with each expression. Do this by providing the placeholder name wrapped in `:`
+ * characters directly after the expression. These placeholder names are stripped out of the
+ * rendered localized string.
+ *
+ * For example, to name the `item.length` expression placeholder `itemCount` you write:
+ *
+ * ```ts
+ * $localize `There are ${item.length}:itemCount: items`;
+ * ```
+ *
+ * If you need to use a `:` character directly an expression you must either provide a name or you
+ * can escape the `:` by preceding it with a backslash:
+ *
+ * For example:
+ *
+ * ```ts
+ * $localize `${label}:label:: ${}`
+ * // or
+ * $localize `${label}\: ${}`
+ * ```
+ *
+ * **Processing localized strings:**
+ *
+ * There are three scenarios:
+ *
+ * * **compile-time inlining**: the `$localize` tag is transformed at compile time by a
+ * transpiler,
+ * removing the tag and replacing the template literal string with a translated literal string
+ * from a collection of translations provided to the transpilation tool.
+ *
+ * * **run-time evaluation**: the `$localize` tag is a run-time function that replaces and
+ * reorders
+ * the parts (static strings and expressions) of the template literal string with strings from a
+ * collection of translations loaded at run-time.
+ *
+ * * **pass-through evaluation**: the `$localize` tag is a run-time function that simply evaluates
+ * the original template literal string without applying any translations to the parts. This
+ * version
+ * is used during development or where there is no need to translate the localized template
+ * literals.
+ *
+ * @param messageParts a collection of the static parts of the template string.
+ * @param expressions a collection of the values of each placeholder in the template string.
+ * @returns the translated string, with the `messageParts` and `expressions` interleaved together.
+ */
+ const $localize: LocalizeFn;
+}
diff --git a/packages/localize/init/package.json b/packages/localize/init/package.json
new file mode 100644
index 0000000000..e63f637630
--- /dev/null
+++ b/packages/localize/init/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "@angular/localize/init",
+ "typings": "./index.d.ts",
+ "main": "../bundles/localize-init.umd.js",
+ "module": "../fesm5/init.js",
+ "es2015": "../fesm2015/init.js",
+ "esm5": "../esm5/init/index.js",
+ "esm2015": "../esm2015/init/index.js",
+ "fesm5": "../fesm5/init.js",
+ "fesm2015": "../fesm2015/init.js"
+}
diff --git a/packages/localize/run_time/index.ts b/packages/localize/localize.ts
similarity index 76%
rename from packages/localize/run_time/index.ts
rename to packages/localize/localize.ts
index 24b88c6c0a..c8eb15048d 100644
--- a/packages/localize/run_time/index.ts
+++ b/packages/localize/localize.ts
@@ -6,4 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
-export {clearTranslations, loadTranslations} from './src/translate';
+// This file contains the public API of the `@angular/localize` entry-point
+
+export {clearTranslations, loadTranslations} from './src/translate';
\ No newline at end of file
diff --git a/packages/localize/package.json b/packages/localize/package.json
index 879a415146..e988e5334f 100644
--- a/packages/localize/package.json
+++ b/packages/localize/package.json
@@ -5,8 +5,8 @@
"main": "./bundles/localize.umd.js",
"module": "./fesm5/localize.js",
"es2015": "./fesm2015/localize.js",
- "esm5": "./esm5/localize.js",
- "esm2015": "./esm2015/localize.js",
+ "esm5": "./esm5/index.js",
+ "esm2015": "./esm2015/index.js",
"fesm5": "./fesm5/localize.js",
"fesm2015": "./fesm2015/localize.js",
"typings": "./index.d.ts",
@@ -19,7 +19,11 @@
"ng-update": {
"packageGroup": "NG_UPDATE_PACKAGE_GROUP"
},
- "sideEffects": true,
+ "sideEffects": [
+ "**/init/index.js",
+ "**/init.js",
+ "**/localize-init.umd.js"
+ ],
"engines": {
"node": ">=8.0"
}
diff --git a/packages/localize/run_time/package.json b/packages/localize/run_time/package.json
deleted file mode 100644
index e3380819f8..0000000000
--- a/packages/localize/run_time/package.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "name": "@angular/localize/run_time",
- "typings": "./index.d.ts",
- "main": "../bundles/localize-run_time.umd.js",
- "module": "../fesm5/run_time.js",
- "es2015": "../fesm2015/run_time.js",
- "esm5": "../esm5/run_time/run_time.js",
- "esm2015": "../esm2015/run_time/run_time.js",
- "fesm5": "../fesm5/run_time.js",
- "fesm2015": "../fesm2015/run_time.js",
- "sideEffects": false
-}
diff --git a/packages/localize/run_time/src/translate.ts b/packages/localize/run_time/src/translate.ts
deleted file mode 100644
index 27c926e0ce..0000000000
--- a/packages/localize/run_time/src/translate.ts
+++ /dev/null
@@ -1,161 +0,0 @@
-/**
- * @license
- * Copyright Google Inc. All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.io/license
- */
-import {LocalizeFn} from '@angular/localize';
-
-/**
- * We augment the `$localize` object to also store the translations.
- *
- * Note that because the TRANSLATIONS are attached to a global object, they will be shared between
- * all applications that are running in a single page of the browser.
- */
-declare const $localize: LocalizeFn&{TRANSLATIONS: {[key: string]: ParsedTranslation}};
-
-/**
- * A map of translations.
- *
- * The key is the original translation message, the value is the translated message.
- *
- * The format of these translation message strings uses `{$marker}` to indicate a placeholder.
- */
-export interface Translations { [translationKey: string]: string; }
-
-/**
- * A translation message that has been processed to extract the message parts and placeholders.
- *
- * This is the format used by the runtime inlining to translate messages.
- */
-export interface ParsedTranslation {
- messageParts: TemplateStringsArray;
- placeholderNames: string[];
-}
-
-/**
- * A localized message that has been processed to compute the translation key for looking up the
- * appropriate translation.
- */
-export interface ParsedMessage {
- translationKey: string;
- substitutions: {[placeholderName: string]: any};
-}
-
-/**
- * The character used to mark the start and end of a placeholder name.
- */
-const PLACEHOLDER_NAME_MARKER = ':';
-
-/**
- * Load translations for `$localize`.
- *
- * The given `translations` are processed and added to a lookup based on their translation key.
- * A new translation will overwrite a previous translation if it has the same key.
- */
-export function loadTranslations(translations: Translations) {
- // Ensure the translate function exists
- if (!$localize.translate) {
- $localize.translate = translate;
- }
- if (!$localize.TRANSLATIONS) {
- $localize.TRANSLATIONS = {};
- }
- Object.keys(translations).forEach(key => {
- $localize.TRANSLATIONS[key] = parseTranslation(translations[key]);
- });
-}
-
-/**
- * Remove all translations for `$localize`.
- */
-export function clearTranslations() {
- $localize.TRANSLATIONS = {};
-}
-
-/**
- * Translate the text of the given message, using the loaded translations.
- *
- * This function may reorder (or remove) substitutions as indicated in the matching translation.
- */
-export function translate(messageParts: TemplateStringsArray, substitutions: readonly any[]):
- [TemplateStringsArray, readonly any[]] {
- const message = parseMessage(messageParts, substitutions);
- const translation = $localize.TRANSLATIONS[message.translationKey];
- const result: [TemplateStringsArray, readonly any[]] =
- (translation === undefined ? [messageParts, substitutions] : [
- translation.messageParts,
- translation.placeholderNames.map(placeholder => message.substitutions[placeholder])
- ]);
- return result;
-}
-
-/////////////
-// Helpers
-
-/**
- * Parse the `messageParts` and `placeholderNames` out of a translation key.
- *
- * @param translationKey the message to be parsed.
- */
-export function parseTranslation(translationKey: string): ParsedTranslation {
- const parts = translationKey.split(/{\$([^}]*)}/);
- const messageParts = [parts[0]];
- const placeholderNames: string[] = [];
- for (let i = 1; i < parts.length - 1; i += 2) {
- placeholderNames.push(parts[i]);
- messageParts.push(`${parts[i + 1]}`);
- }
- const rawMessageParts =
- messageParts.map(part => part.charAt(0) === PLACEHOLDER_NAME_MARKER ? '\\' + part : part);
- return {messageParts: makeTemplateObject(messageParts, rawMessageParts), placeholderNames};
-}
-
-/**
- * Process the `messageParts` and `substitutions` that were passed to the `$localize` tag in order
- * to match it to a translation.
- *
- * Specifically this function computes:
- * * the `translationKey` for looking up an appropriate translation for this message.
- * * a map of placeholder names to substitutions values.
- */
-export function parseMessage(
- messageParts: TemplateStringsArray, expressions: readonly any[]): ParsedMessage {
- const replacements: {[placeholderName: string]: any} = {};
- let translationKey = messageParts[0];
- for (let i = 1; i < messageParts.length; i++) {
- const messagePart = messageParts[i];
- const expression = expressions[i - 1];
- // There is a problem with synthesizing template literals in TS.
- // It is not possible to provide raw values for the `messageParts` and TS is not able to compute
- // them since this requires access to the string in its original (non-existent) source code.
- // Therefore we fall back on the non-raw version if the raw string is empty.
- // This should be OK because synthesized nodes only come from the template compiler and they
- // will always contain placeholder name information.
- // So there will be no escaped placeholder marker character (`:`) directly after a substitution.
- if ((messageParts.raw[i] || messagePart).charAt(0) === PLACEHOLDER_NAME_MARKER) {
- const endOfPlaceholderName = messagePart.indexOf(PLACEHOLDER_NAME_MARKER, 1);
- const placeholderName = messagePart.substring(1, endOfPlaceholderName);
- translationKey += `{$${placeholderName}}${messagePart.substring(endOfPlaceholderName + 1)}`;
- replacements[placeholderName] = expression;
- } else {
- const placeholderName = `ph_${i}`;
- translationKey += `{$${placeholderName}}${messagePart}`;
- replacements[placeholderName] = expression;
- }
- }
- return {translationKey, substitutions: replacements};
-}
-
-/**
- * Make an array of `cooked` strings that also holds the `raw` strings in an additional property.
- *
- * @param cooked The actual values of the `messagePart` strings.
- * @param raw The original raw values of the `messagePart` strings, before escape characters are
- * processed.
- */
-function makeTemplateObject(cooked: string[], raw: string[]): TemplateStringsArray {
- Object.defineProperty(cooked, 'raw', {value: raw});
- return cooked as any;
-}
diff --git a/packages/localize/src/localize/BUILD.bazel b/packages/localize/src/localize/BUILD.bazel
new file mode 100644
index 0000000000..8bf81c7357
--- /dev/null
+++ b/packages/localize/src/localize/BUILD.bazel
@@ -0,0 +1,16 @@
+load("//tools:defaults.bzl", "ts_library")
+
+package(default_visibility = ["//visibility:public"])
+
+ts_library(
+ name = "localize",
+ srcs = glob(
+ [
+ "**/*.ts",
+ ],
+ ),
+ module_name = "@angular/localize/src/localize",
+ deps = [
+ "@npm//@types/node",
+ ],
+)
diff --git a/packages/localize/src/localize/index.ts b/packages/localize/src/localize/index.ts
new file mode 100644
index 0000000000..0f9d07ab32
--- /dev/null
+++ b/packages/localize/src/localize/index.ts
@@ -0,0 +1,9 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+export {_global} from './src/global';
+export {$localize, LocalizeFn, TranslateFn} from './src/localize';
diff --git a/packages/localize/src/global.ts b/packages/localize/src/localize/src/global.ts
similarity index 100%
rename from packages/localize/src/global.ts
rename to packages/localize/src/localize/src/global.ts
diff --git a/packages/localize/src/localize.ts b/packages/localize/src/localize/src/localize.ts
similarity index 100%
rename from packages/localize/src/localize.ts
rename to packages/localize/src/localize/src/localize.ts
diff --git a/packages/localize/run_time/test/BUILD.bazel b/packages/localize/src/localize/test/BUILD.bazel
similarity index 85%
rename from packages/localize/run_time/test/BUILD.bazel
rename to packages/localize/src/localize/test/BUILD.bazel
index a1d6e7ddde..94cde285ef 100644
--- a/packages/localize/run_time/test/BUILD.bazel
+++ b/packages/localize/src/localize/test/BUILD.bazel
@@ -8,8 +8,7 @@ ts_library(
),
deps = [
"//packages:types",
- "//packages/localize",
- "//packages/localize/run_time",
+ "//packages/localize/src/localize",
],
)
diff --git a/packages/localize/test/localize_spec.ts b/packages/localize/src/localize/test/localize_spec.ts
similarity index 97%
rename from packages/localize/test/localize_spec.ts
rename to packages/localize/src/localize/test/localize_spec.ts
index 7c67808f8d..29323254a3 100644
--- a/packages/localize/test/localize_spec.ts
+++ b/packages/localize/src/localize/test/localize_spec.ts
@@ -5,8 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-import '..'; // Ensure $localize is attached to the global scope
-import {TranslateFn} from '../src/localize';
+import {$localize, TranslateFn} from '../src/localize';
describe('$localize tag', () => {
describe('with no `translate()` defined (the default)', () => {
diff --git a/packages/localize/src/translate.ts b/packages/localize/src/translate.ts
new file mode 100644
index 0000000000..1db3e5885e
--- /dev/null
+++ b/packages/localize/src/translate.ts
@@ -0,0 +1,57 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {LocalizeFn} from './localize';
+import {ParsedTranslation, TargetMessage, TranslationKey, parseTranslation, translate as _translate} from './utils/translations';
+
+/**
+ * We augment the `$localize` object to also store the translations.
+ *
+ * Note that because the TRANSLATIONS are attached to a global object, they will be shared between
+ * all applications that are running in a single page of the browser.
+ */
+declare const $localize: LocalizeFn&{TRANSLATIONS: Record};
+
+/**
+ * Load translations for `$localize`.
+ *
+ * The given `translations` are processed and added to a lookup based on their translation key.
+ * A new translation will overwrite a previous translation if it has the same key.
+ *
+ * @publicApi
+ */
+export function loadTranslations(translations: Record) {
+ // Ensure the translate function exists
+ if (!$localize.translate) {
+ $localize.translate = translate;
+ }
+ if (!$localize.TRANSLATIONS) {
+ $localize.TRANSLATIONS = {};
+ }
+ Object.keys(translations).forEach(key => {
+ $localize.TRANSLATIONS[key] = parseTranslation(translations[key]);
+ });
+}
+
+/**
+ * Remove all translations for `$localize`.
+ *
+ * @publicApi
+ */
+export function clearTranslations() {
+ $localize.TRANSLATIONS = {};
+}
+
+/**
+ * Translate the text of the given message, using the loaded translations.
+ *
+ * This function may reorder (or remove) substitutions as indicated in the matching translation.
+ */
+export function translate(messageParts: TemplateStringsArray, substitutions: readonly any[]):
+ [TemplateStringsArray, readonly any[]] {
+ return _translate($localize.TRANSLATIONS, messageParts, substitutions);
+}
diff --git a/packages/localize/src/utils/constants.ts b/packages/localize/src/utils/constants.ts
new file mode 100644
index 0000000000..efcd0d7ab2
--- /dev/null
+++ b/packages/localize/src/utils/constants.ts
@@ -0,0 +1,19 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+/**
+ * The character used to mark the start and end of a placeholder name in a `$localize` tagged
+ * string.
+ *
+ * For example:
+ *
+ * ```
+ * $localize`Hello, ${title}:title:!`;
+ * ```
+ */
+export const PLACEHOLDER_NAME_MARKER = ':';
diff --git a/packages/localize/src/utils/messages.ts b/packages/localize/src/utils/messages.ts
new file mode 100644
index 0000000000..9eed8dca4f
--- /dev/null
+++ b/packages/localize/src/utils/messages.ts
@@ -0,0 +1,81 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {PLACEHOLDER_NAME_MARKER} from './constants';
+import {TranslationKey} from './translations';
+
+/**
+ * A string containing a translation source message.
+ *
+ * I.E. the message that indicates what will be translated from.
+ *
+ * Uses `{$placeholder-name}` to indicate a placeholder.
+ */
+export type SourceMessage = string;
+
+/**
+ * Information parsed from a `$localize` tagged string that is used to translate it.
+ *
+ * For example:
+ *
+ * ```
+ * const name = 'Jo Bloggs';
+ * $localize`Hello ${name}:title!`;
+ * ```
+ *
+ * May be parsed into:
+ *
+ * ```
+ * {
+ * translationKey: 'Hello {$title}!',
+ * substitutions: { title: 'Jo Bloggs' },
+ * }
+ * ```
+ */
+export interface ParsedMessage {
+ /**
+ * The key used to look up the appropriate translation target.
+ */
+ translationKey: TranslationKey;
+ /**
+ * A mapping of placeholder names to substitution values.
+ */
+ substitutions: Record;
+}
+
+/**
+ * Parse a `$localize` tagged string into a structure that can be used for translation.
+ *
+ * See `ParsedMessage` for an example.
+ */
+export function parseMessage(
+ messageParts: TemplateStringsArray, expressions: readonly any[]): ParsedMessage {
+ const replacements: {[placeholderName: string]: any} = {};
+ let translationKey = messageParts[0];
+ for (let i = 1; i < messageParts.length; i++) {
+ const messagePart = messageParts[i];
+ const expression = expressions[i - 1];
+ // There is a problem with synthesizing template literals in TS.
+ // It is not possible to provide raw values for the `messageParts` and TS is not able to compute
+ // them since this requires access to the string in its original (non-existent) source code.
+ // Therefore we fall back on the non-raw version if the raw string is empty.
+ // This should be OK because synthesized nodes only come from the template compiler and they
+ // will always contain placeholder name information.
+ // So there will be no escaped placeholder marker character (`:`) directly after a substitution.
+ if ((messageParts.raw[i] || messagePart).charAt(0) === PLACEHOLDER_NAME_MARKER) {
+ const endOfPlaceholderName = messagePart.indexOf(PLACEHOLDER_NAME_MARKER, 1);
+ const placeholderName = messagePart.substring(1, endOfPlaceholderName);
+ translationKey += `{$${placeholderName}}${messagePart.substring(endOfPlaceholderName + 1)}`;
+ replacements[placeholderName] = expression;
+ } else {
+ const placeholderName = `ph_${i}`;
+ translationKey += `{$${placeholderName}}${messagePart}`;
+ replacements[placeholderName] = expression;
+ }
+ }
+ return {translationKey, substitutions: replacements};
+}
diff --git a/packages/localize/src/utils/translations.ts b/packages/localize/src/utils/translations.ts
new file mode 100644
index 0000000000..8345929989
--- /dev/null
+++ b/packages/localize/src/utils/translations.ts
@@ -0,0 +1,97 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {PLACEHOLDER_NAME_MARKER} from './constants';
+import {SourceMessage, parseMessage} from './messages';
+
+/**
+ * A key used to lookup a `TargetMessage` in a hash map.
+ */
+export type TranslationKey = SourceMessage;
+
+/**
+ * A string containing a translation target message.
+ *
+ * I.E. the message that indicates what will be translated to.
+ *
+ * Uses `{$placeholder-name}` to indicate a placeholder.
+ */
+export type TargetMessage = string;
+
+/**
+ * A translation message that has been processed to extract the message parts and placeholders.
+ */
+export interface ParsedTranslation {
+ messageParts: TemplateStringsArray;
+ placeholderNames: string[];
+}
+
+/**
+ * The internal structure used by the runtime localization to translate messages.
+ */
+export type ParsedTranslations = Record;
+
+
+/**
+ * Translate the text of the `$localize` tagged-string (i.e. `messageParts` and
+ * `substitutions`) using the given `translations`.
+ *
+ * The tagged-string is parsed to extract its `translationKey` which is used to find an appropriate
+ * `ParsedTranslation`.
+ *
+ * If one is found then it is used to translate the message into a new set of `messageParts` and
+ * `substitutions`.
+ * The translation may reorder (or remove) substitutions as appropriate.
+ *
+ * If no translation matches then the original `messageParts` and `substitutions` are returned
+ */
+export function translate(
+ translations: Record, messageParts: TemplateStringsArray,
+ substitutions: readonly any[]): [TemplateStringsArray, readonly any[]] {
+ const message = parseMessage(messageParts, substitutions);
+ const translation = translations[message.translationKey];
+ if (translation !== undefined) {
+ return [
+ translation.messageParts,
+ translation.placeholderNames.map(placeholder => message.substitutions[placeholder])
+ ];
+ } else {
+ return [messageParts, substitutions];
+ }
+}
+
+/**
+ * Parse the `messageParts` and `placeholderNames` out of a target `message`.
+ *
+ * Used by `loadTranslations()` to convert target message strings into a structure that is more
+ * appropriate for doing translation.
+ *
+ * @param message the message to be parsed.
+ */
+export function parseTranslation(message: TargetMessage): ParsedTranslation {
+ const parts = message.split(/{\$([^}]*)}/);
+ const messageParts = [parts[0]];
+ const placeholderNames: string[] = [];
+ for (let i = 1; i < parts.length - 1; i += 2) {
+ placeholderNames.push(parts[i]);
+ messageParts.push(`${parts[i + 1]}`);
+ }
+ const rawMessageParts =
+ messageParts.map(part => part.charAt(0) === PLACEHOLDER_NAME_MARKER ? '\\' + part : part);
+ return {messageParts: makeTemplateObject(messageParts, rawMessageParts), placeholderNames};
+}
+
+/**
+ * Create the specialized array that is passed to tagged-string tag functions.
+ *
+ * @param cooked The message parts with their escape codes processed.
+ * @param raw The message parts with their escaped codes as-is.
+ */
+export function makeTemplateObject(cooked: string[], raw: string[]): TemplateStringsArray {
+ Object.defineProperty(cooked, 'raw', {value: raw});
+ return cooked as any;
+}
diff --git a/packages/localize/test/BUILD.bazel b/packages/localize/test/BUILD.bazel
index aaf6c460eb..5a12e06dc4 100644
--- a/packages/localize/test/BUILD.bazel
+++ b/packages/localize/test/BUILD.bazel
@@ -9,6 +9,7 @@ ts_library(
deps = [
"//packages:types",
"//packages/localize",
+ "//packages/localize/init",
],
)
diff --git a/packages/localize/run_time/test/translate_spec.ts b/packages/localize/test/translate_spec.ts
similarity index 97%
rename from packages/localize/run_time/test/translate_spec.ts
rename to packages/localize/test/translate_spec.ts
index 78eaa8422f..d54f2793cc 100644
--- a/packages/localize/run_time/test/translate_spec.ts
+++ b/packages/localize/test/translate_spec.ts
@@ -5,8 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-// Ensure that `$localize` is loaded to the global scope.
-import '@angular/localize';
+import '@angular/localize/init';
import {clearTranslations, loadTranslations} from '../src/translate';
describe('$localize tag with translations', () => {
diff --git a/packages/localize/test/utils/messages_spec.ts b/packages/localize/test/utils/messages_spec.ts
new file mode 100644
index 0000000000..9eb8cdcd3f
--- /dev/null
+++ b/packages/localize/test/utils/messages_spec.ts
@@ -0,0 +1,47 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {parseMessage} from '../../src/utils/messages';
+import {makeTemplateObject} from '../../src/utils/translations';
+
+describe('messages utils', () => {
+ describe('parseMessage', () => {
+ it('should compute the translation key', () => {
+ const message = parseMessage(
+ makeTemplateObject(['a', ':one:b', ':two:c'], ['a', ':one:b', ':two:c']), [1, 2]);
+ expect(message.translationKey).toEqual('a{$one}b{$two}c');
+ });
+
+ it('should compute the translation key, inferring placeholder names if not given', () => {
+ const message = parseMessage(makeTemplateObject(['a', 'b', 'c'], ['a', 'b', 'c']), [1, 2]);
+ expect(message.translationKey).toEqual('a{$ph_1}b{$ph_2}c');
+ });
+
+ it('should compute the translation key, ignoring escaped placeholder names', () => {
+ const message = parseMessage(
+ makeTemplateObject(['a', ':one:b', ':two:c'], ['a', '\\:one:b', '\\:two:c']), [1, 2]);
+ expect(message.translationKey).toEqual('a{$ph_1}:one:b{$ph_2}:two:c');
+ });
+
+ it('should compute the translation key, handling empty raw values', () => {
+ const message =
+ parseMessage(makeTemplateObject(['a', ':one:b', ':two:c'], ['', '', '']), [1, 2]);
+ expect(message.translationKey).toEqual('a{$one}b{$two}c');
+ });
+
+ it('should build a map of named placeholders to expressions', () => {
+ const message = parseMessage(
+ makeTemplateObject(['a', ':one:b', ':two:c'], ['a', ':one:b', ':two:c']), [1, 2]);
+ expect(message.substitutions).toEqual({one: 1, two: 2});
+ });
+
+ it('should build a map of implied placeholders to expressions', () => {
+ const message = parseMessage(makeTemplateObject(['a', 'b', 'c'], ['a', 'b', 'c']), [1, 2]);
+ expect(message.substitutions).toEqual({ph_1: 1, ph_2: 2});
+ });
+ });
+});
diff --git a/packages/localize/test/utils/translations_spec.ts b/packages/localize/test/utils/translations_spec.ts
new file mode 100644
index 0000000000..a9158f0c15
--- /dev/null
+++ b/packages/localize/test/utils/translations_spec.ts
@@ -0,0 +1,164 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import {ParsedTranslation, TargetMessage, TranslationKey, makeTemplateObject, parseTranslation, translate} from '../../src/utils/translations';
+
+describe('utils', () => {
+ describe('makeTemplateObject', () => {
+ it('should return an array containing the cooked items', () => {
+ const template =
+ makeTemplateObject(['cooked-a', 'cooked-b', 'cooked-c'], ['raw-a', 'raw-b', 'raw-c']);
+ expect(template).toEqual(['cooked-a', 'cooked-b', 'cooked-c']);
+ });
+
+ it('should return an array that has a raw property containing the raw items', () => {
+ const template =
+ makeTemplateObject(['cooked-a', 'cooked-b', 'cooked-c'], ['raw-a', 'raw-b', 'raw-c']);
+ expect(template.raw).toEqual(['raw-a', 'raw-b', 'raw-c']);
+ });
+ });
+
+ describe('parseTranslation', () => {
+ it('should extract the messageParts as a TemplateStringsArray', () => {
+ const translation = parseTranslation('a{$one}b{$two}c');
+ expect(translation.messageParts).toEqual(['a', 'b', 'c']);
+ expect(translation.messageParts.raw).toEqual(['a', 'b', 'c']);
+ });
+
+ it('should extract the messageParts with leading expression as a TemplateStringsArray', () => {
+ const translation = parseTranslation('{$one}a{$two}b');
+ expect(translation.messageParts).toEqual(['', 'a', 'b']);
+ expect(translation.messageParts.raw).toEqual(['', 'a', 'b']);
+ });
+
+ it('should extract the messageParts with trailing expression as a TemplateStringsArray', () => {
+ const translation = parseTranslation('a{$one}b{$two}');
+ expect(translation.messageParts).toEqual(['a', 'b', '']);
+ expect(translation.messageParts.raw).toEqual(['a', 'b', '']);
+ });
+
+ it('should extract the messageParts with escaped characters as a TemplateStringsArray', () => {
+ const translation = parseTranslation('a{$one}\nb\n{$two}c');
+ expect(translation.messageParts).toEqual(['a', '\nb\n', 'c']);
+ // `messageParts.raw` are not actually escaped as they are not generally used by `$localize`.
+ // See the "escaped placeholders" test below...
+ expect(translation.messageParts.raw).toEqual(['a', '\nb\n', 'c']);
+ });
+
+ it('should extract the messageParts with escaped placeholders as a TemplateStringsArray',
+ () => {
+ const translation = parseTranslation('a{$one}:marker:b{$two}c');
+ expect(translation.messageParts).toEqual(['a', ':marker:b', 'c']);
+ // A `messagePart` that starts with a placeholder marker does get escaped in
+ // `messageParts.raw` as this is used by `$localize`.
+ expect(translation.messageParts.raw).toEqual(['a', '\\:marker:b', 'c']);
+ });
+
+ it('should extract the placeholder names, in order', () => {
+ const translation = parseTranslation('a{$one}b{$two}c');
+ expect(translation.placeholderNames).toEqual(['one', 'two']);
+ });
+
+ it('should handle a translation with no substitutions', () => {
+ const translation = parseTranslation('abc');
+ expect(translation.messageParts).toEqual(['abc']);
+ expect(translation.messageParts.raw).toEqual(['abc']);
+ expect(translation.placeholderNames).toEqual([]);
+ });
+
+ it('should handle a translation with only substitutions', () => {
+ const translation = parseTranslation('{$one}{$two}');
+ expect(translation.messageParts).toEqual(['', '', '']);
+ expect(translation.messageParts.raw).toEqual(['', '', '']);
+ expect(translation.placeholderNames).toEqual(['one', 'two']);
+ });
+ });
+
+ describe('translate', () => {
+ it('(with identity translations) should render template literals as-is', () => {
+ const translations = {
+ 'abc': 'abc',
+ 'abc{$ph_1}': 'abc{$ph_1}',
+ 'abc{$ph_1}def': 'abc{$ph_1}def',
+ 'abc{$ph_1}def{$ph_2}': 'abc{$ph_1}def{$ph_2}',
+ 'Hello, {$ph_1}!': 'Hello, {$ph_1}!',
+ };
+ expect(doTranslate(translations, parts `abc`)).toEqual(parts `abc`);
+ expect(doTranslate(translations, parts `abc${1 + 2 + 3}`)).toEqual(parts `abc${1 + 2 + 3}`);
+ expect(doTranslate(translations, parts `abc${1 + 2 + 3}def`))
+ .toEqual(parts `abc${1 + 2 + 3}def`);
+ expect(doTranslate(translations, parts `abc${1 + 2 + 3}def${4 + 5 + 6}`))
+ .toEqual(parts `abc${1 + 2 + 3}def${4 + 5 + 6}`);
+ const getName = () => 'World';
+ expect(doTranslate(translations, parts `Hello, ${getName()}!`))
+ .toEqual(parts `Hello, ${'World'}!`);
+ });
+
+ it('(with upper-casing translations) should render template literals with messages upper-cased',
+ () => {
+ const translations = {
+ 'abc': 'ABC',
+ 'abc{$ph_1}': 'ABC{$ph_1}',
+ 'abc{$ph_1}def': 'ABC{$ph_1}DEF',
+ 'abc{$ph_1}def{$ph_2}': 'ABC{$ph_1}DEF{$ph_2}',
+ 'Hello, {$ph_1}!': 'HELLO, {$ph_1}!',
+ };
+ expect(doTranslate(translations, parts `abc`)).toEqual(parts `ABC`);
+ expect(doTranslate(translations, parts `abc${1 + 2 + 3}`))
+ .toEqual(parts `ABC${1 + 2 + 3}`);
+ expect(doTranslate(translations, parts `abc${1 + 2 + 3}def`))
+ .toEqual(parts `ABC${1 + 2 + 3}DEF`);
+ expect(doTranslate(translations, parts `abc${1 + 2 + 3}def${4 + 5 + 6}`))
+ .toEqual(parts `ABC${1 + 2 + 3}DEF${4 + 5 + 6}`);
+ const getName = () => 'World';
+ expect(doTranslate(translations, parts `Hello, ${getName()}!`))
+ .toEqual(parts `HELLO, ${'World'}!`);
+ });
+
+ it('(with translations to reverse expressions) should render template literals with expressions reversed',
+ () => {
+ const translations = {
+ 'abc{$ph_1}def{$ph_2} - Hello, {$ph_3}!': 'abc{$ph_3}def{$ph_2} - Hello, {$ph_1}!',
+ };
+ const getName = () => 'World';
+ expect(doTranslate(
+ translations, parts `abc${1 + 2 + 3}def${4 + 5 + 6} - Hello, ${getName()}!`))
+ .toEqual(parts `abc${'World'}def${4 + 5 + 6} - Hello, ${1 + 2 + 3}!`);
+ });
+
+ it('(with translations to remove expressions) should render template literals with expressions removed',
+ () => {
+ const translations = {
+ 'abc{$ph_1}def{$ph_2} - Hello, {$ph_3}!': 'abc{$ph_1} - Hello, {$ph_3}!',
+ };
+ const getName = () => 'World';
+ expect(doTranslate(
+ translations, parts `abc${1 + 2 + 3}def${4 + 5 + 6} - Hello, ${getName()}!`))
+ .toEqual(parts `abc${1 + 2 + 3} - Hello, ${'World'}!`);
+ });
+
+ function parts(messageParts: TemplateStringsArray, ...substitutions: any[]):
+ [TemplateStringsArray, any[]] {
+ return [messageParts, substitutions];
+ }
+
+ function parseTranslations(translations: Record):
+ Record {
+ const parsedTranslations: Record = {};
+ Object.keys(translations).forEach(key => {
+ parsedTranslations[key] = parseTranslation(translations[key]);
+ });
+ return parsedTranslations;
+ }
+
+ function doTranslate(
+ translations: Record,
+ message: [TemplateStringsArray, any[]]): [TemplateStringsArray, readonly any[]] {
+ return translate(parseTranslations(translations), message[0], message[1]);
+ }
+ });
+});
diff --git a/test-main.js b/test-main.js
index 1fea998c6a..45bbc1c456 100644
--- a/test-main.js
+++ b/test-main.js
@@ -51,7 +51,8 @@ System.config({
'@angular/router': {main: 'index.js', defaultExtension: 'js'},
'@angular/http/testing': {main: 'index.js', defaultExtension: 'js'},
'@angular/http': {main: 'index.js', defaultExtension: 'js'},
- '@angular/localize/run_time': {main: 'index.js', defaultExtension: 'js'},
+ '@angular/localize/src/localize': {main: 'index.js', defaultExtension: 'js'},
+ '@angular/localize/init': {main: 'index.js', defaultExtension: 'js'},
'@angular/localize': {main: 'index.js', defaultExtension: 'js'},
'@angular/upgrade/static/testing': {main: 'index.js', defaultExtension: 'js'},
'@angular/upgrade/static': {main: 'index.js', defaultExtension: 'js'},
diff --git a/tools/public_api_guard/localize/localize.d.ts b/tools/public_api_guard/localize/localize.d.ts
new file mode 100644
index 0000000000..c3d5f94d6e
--- /dev/null
+++ b/tools/public_api_guard/localize/localize.d.ts
@@ -0,0 +1,3 @@
+export declare function clearTranslations(): void;
+
+export declare function loadTranslations(translations: Record): void;