Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ivy): i18n - inline current locale at compile-time
During compile-time translation inlining, the `$localize.locale` expression will now be replaced with a string literal containing the current locale of the translations.
- Loading branch information
1 parent
c328512
commit 3aca5a2
Showing
4 changed files
with
162 additions
and
0 deletions.
There are no files selected for viewing
62 changes: 62 additions & 0 deletions
62
packages/localize/src/tools/src/translate/source_files/locale_plugin.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/** | ||
* @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 {NodePath, PluginObj} from '@babel/core'; | ||
import {LogicalExpression, MemberExpression, stringLiteral} from '@babel/types'; | ||
|
||
import {TranslatePluginOptions, isLocalize} from './source_file_utils'; | ||
|
||
/** | ||
* This Babel plugin will replace the following code forms with a string literal containing the | ||
* given `locale`. | ||
* | ||
* * `$localize.locale` -> `"locale"` | ||
* * `$localize && $localize.locale` -> `"locale"` | ||
* * `xxx && $localize && $localize.locale` -> `"xxx && locale"` | ||
* * `$localize.locale || default` -> `"locale" || default` | ||
* | ||
* @param locale The name of the locale to inline into the code. | ||
* @param options Additional options including the name of the `$localize` function. | ||
*/ | ||
export function makeLocalePlugin( | ||
locale: string, {localizeName = '$localize'}: TranslatePluginOptions = {}): PluginObj { | ||
return { | ||
visitor: { | ||
MemberExpression(expression: NodePath<MemberExpression>) { | ||
const obj = expression.get('object'); | ||
if (!isLocalize(obj, localizeName)) { | ||
return; | ||
} | ||
const property = expression.get('property') as NodePath; | ||
if (!property.isIdentifier() || property.node.name !== 'locale') { | ||
return; | ||
} | ||
debugger; | ||
// Check for the `$localize.locale` being guarded by a check on the existence of | ||
// `$localize`. | ||
const parent = expression.parentPath; | ||
if (parent.isLogicalExpression() && parent.node.operator === '&&' && | ||
parent.get('right') === expression) { | ||
const left = parent.get('left'); | ||
if (isLocalize(left, localizeName)) { | ||
// Replace `$localize && $localize.locale` with `$localize.locale` | ||
parent.replaceWith(expression); | ||
} else if ( | ||
left.isLogicalExpression() && left.node.operator === '&&' && | ||
isLocalize(left.get('right'), localizeName)) { | ||
// The `$localize` is part of a preceding logical AND. | ||
// Replace XXX && $localize && $localize.locale` with `XXX && $localize.locale` | ||
left.replaceWith(left.get('left')); | ||
} | ||
} | ||
|
||
// Replace the `$localize.locale` with the string literal | ||
expression.replaceWith(stringLiteral(locale)); | ||
} | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
packages/localize/src/tools/test/translate/source_files/locale_plugin_spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/** | ||
* @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 {transformSync} from '@babel/core'; | ||
import {makeLocalePlugin} from '../../../src/translate/source_files/locale_plugin'; | ||
|
||
describe('makeEs2015Plugin', () => { | ||
it('should replace $localize.locale with the locale string', () => { | ||
const input = '$localize.locale;'; | ||
const output = transformSync(input, {plugins: [makeLocalePlugin('fr')]}) !; | ||
expect(output.code).toEqual('"fr";'); | ||
}); | ||
|
||
it('should replace $localize.locale with the locale string in the context of a variable assignment', | ||
() => { | ||
const input = 'const a = $localize.locale;'; | ||
const output = transformSync(input, {plugins: [makeLocalePlugin('fr')]}) !; | ||
expect(output.code).toEqual('const a = "fr";'); | ||
}); | ||
|
||
it('should replace $localize.locale with the locale string in the context of a binary expression', | ||
() => { | ||
const input = '$localize.locale || "default";'; | ||
const output = transformSync(input, {plugins: [makeLocalePlugin('fr')]}) !; | ||
expect(output.code).toEqual('"fr" || "default";'); | ||
}); | ||
|
||
it('should remove reference to `$localize` if used to guard the locale', () => { | ||
const input = '$localize && $localize.locale;'; | ||
const output = transformSync(input, {plugins: [makeLocalePlugin('fr')]}) !; | ||
expect(output.code).toEqual('"fr";'); | ||
}); | ||
|
||
it('should remove reference to `$localize` if used in a longer logical expression to guard the locale', | ||
() => { | ||
const input = 'someValue && $localize && $localize.locale;'; | ||
const output = transformSync(input, {plugins: [makeLocalePlugin('fr')]}) !; | ||
expect(output.code).toEqual('someValue && "fr";'); | ||
}); | ||
|
||
it('should ignore properties on $localize other than `locale`', () => { | ||
const input = '$localize.notLocale;'; | ||
const output = transformSync(input, {plugins: [makeLocalePlugin('fr')]}) !; | ||
expect(output.code).toEqual('$localize.notLocale;'); | ||
}); | ||
|
||
it('should ignore indexed property on $localize', () => { | ||
const input = '$localize["locale"];'; | ||
const output = transformSync(input, {plugins: [makeLocalePlugin('fr')]}) !; | ||
expect(output.code).toEqual('$localize["locale"];'); | ||
}); | ||
|
||
it('should ignore `locale` on objects other than $localize', () => { | ||
const input = '$notLocalize.locale;'; | ||
const output = transformSync(input, {plugins: [makeLocalePlugin('fr')]}) !; | ||
expect(output.code).toEqual('$notLocalize.locale;'); | ||
}); | ||
|
||
it('should ignore `$localize.locale` if `$localize` is not global', () => { | ||
const input = 'const $localize = {};\n$localize.locale;'; | ||
const output = transformSync(input, {plugins: [makeLocalePlugin('fr')]}) !; | ||
expect(output.code).toEqual('const $localize = {};\n$localize.locale;'); | ||
}); | ||
|
||
it('should ignore `locale` if it is not directly accessed from `$localize`', () => { | ||
const input = 'const {locale} = $localize;\nconst a = locale;'; | ||
const output = transformSync(input, {plugins: [makeLocalePlugin('fr')]}) !; | ||
expect(output.code).toEqual('const {locale} = $localize;\nconst a = locale;'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters