Skip to content

Commit

Permalink
Update I18n modules to support loading automatically bundled CLDR data (
Browse files Browse the repository at this point in the history
#657)

* Adapt i18n to use Globalize/CLDR for message resolution

* Update test name

* remove debugger

* unit tests

* add i18n api for loading and setting locale info

* fix unit tests

* testing

* supported locales include default locale

* Use diffProperty to influence the locale property

* small tweaks

* Support proper fallback for locale in mixin

* Support i18nBundle property

* remove unused var

* unit tests

* fix cldr loader

* fallback to the full cldr supplemental data

* Use fallback supplemental cldr in error scenarios

* use warn not log

* fix tests

* clean up the i18n cldr fallback and resolution

* split out the core i18n functionality for clarity

* tidy

* address review feedback

* Use property locale

* cache bundles

* package-lock
  • Loading branch information
agubler authored Feb 11, 2020
1 parent b61c6ff commit 6224e88
Show file tree
Hide file tree
Showing 33 changed files with 1,369 additions and 3,339 deletions.
79 changes: 39 additions & 40 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@types/cldrjs": "0.4.20",
"@types/globalize": "0.0.34",
"@webcomponents/webcomponentsjs": "1.1.0",
"cldr-core": "36.0.0",
"cldrjs": "0.5.0",
"cross-fetch": "3.0.2",
"css-select-umd": "1.3.0-rc0",
Expand All @@ -52,6 +53,7 @@
"immutable": "3.8.2",
"intersection-observer": "0.4.2",
"pepjs": "0.4.2",
"requirejs-text": "2.0.15",
"resize-observer-polyfill": "1.5.0",
"tslib": "1.9.3",
"web-animations-js": "2.3.1",
Expand All @@ -71,7 +73,7 @@
"benchmark": "^1.0.0",
"bootstrap": "^3.3.7",
"chromedriver": "2.40.0",
"cldr-data": "32.0.1",
"cldr-data": "36.0.0",
"codecov": "~3.0.4",
"cpx": "~1.5.0",
"husky": "~0.14.3",
Expand Down
39 changes: 23 additions & 16 deletions src/core/decorators/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ export interface InjectConfig {
getProperties: GetProperties;
}

export function getInjector(instance: WidgetBase & { own: Function }, name: RegistryLabel) {
const injectorItem = instance.registry.getInjector(name);
if (injectorItem) {
const { injector, invalidator } = injectorItem;
const registeredInjectors = registeredInjectorsMap.get(instance) || [];
if (registeredInjectors.length === 0) {
registeredInjectorsMap.set(instance, registeredInjectors);
}
if (registeredInjectors.indexOf(injectorItem) === -1) {
instance.own(
invalidator.on('invalidate', () => {
instance.invalidate();
})
);
registeredInjectors.push(injectorItem);
}
return injector;
}
}

/**
* Decorator retrieves an injector from an available registry using the name and
* calls the `getProperties` function with the payload from the injector
Expand All @@ -41,23 +61,10 @@ export interface InjectConfig {
* @param InjectConfig the inject configuration
*/
export function inject({ name, getProperties }: InjectConfig) {
return handleDecorator((target, propertyKey) => {
return handleDecorator((target) => {
beforeProperties(function(this: WidgetBase & { own: Function }, properties: any) {
const injectorItem = this.registry.getInjector(name);
if (injectorItem) {
const { injector, invalidator } = injectorItem;
const registeredInjectors = registeredInjectorsMap.get(this) || [];
if (registeredInjectors.length === 0) {
registeredInjectorsMap.set(this, registeredInjectors);
}
if (registeredInjectors.indexOf(injectorItem) === -1) {
this.own(
invalidator.on('invalidate', () => {
this.invalidate();
})
);
registeredInjectors.push(injectorItem);
}
const injector = getInjector(this, name);
if (injector) {
return getProperties(injector(), properties);
}
})(target);
Expand Down
118 changes: 47 additions & 71 deletions src/core/middleware/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
/* tslint:disable:interface-name */
import i18nCore, { Bundle, formatMessage, getCachedMessages, Messages } from '../../i18n/i18n';
import { create, invalidator, getRegistry } from '../vdom';
import { localizeBundle, Bundle, Messages, setLocale, getCurrentLocale } from '../../i18n/i18n';
import { create, invalidator, getRegistry, diffProperty } from '../vdom';
import injector from './injector';
import Map from '../../shim/Map';
import Injector from '../Injector';
import Registry from '../Registry';
import { I18nProperties, LocalizedMessages, LocaleData } from '../interfaces';
export { LocalizedMessages, I18nProperties, LocaleData } from './../interfaces';
import { isThenable } from '../../shim/Promise';

export const INJECTOR_KEY = '__i18n_injector';

Expand All @@ -19,96 +17,74 @@ export function registerI18nInjector(localeData: LocaleData, registry: Registry)
return injector;
}

const factory = create({ invalidator, injector, getRegistry }).properties<I18nProperties>();
const factory = create({ invalidator, injector, getRegistry, diffProperty }).properties<I18nProperties>();

export const i18n = factory(({ properties, middleware: { invalidator, injector, getRegistry } }) => {
export const i18n = factory(({ properties, middleware: { invalidator, injector, getRegistry, diffProperty } }) => {
const i18nInjector = injector.get(INJECTOR_KEY);
if (!i18nInjector) {
const registry = getRegistry();
if (registry) {
registerI18nInjector({}, registry.base);
}
}
injector.subscribe(INJECTOR_KEY);

function getLocaleMessages(bundle: Bundle<Messages>): Messages | void {
let { locale } = properties();
if (!locale) {
const injectedLocale = injector.get<Injector<LocaleData>>(INJECTOR_KEY);
if (injectedLocale) {
locale = injectedLocale.get().locale;
diffProperty('locale', properties, (current, next) => {
const localeDataInjector = injector.get<Injector<LocaleData | undefined>>(INJECTOR_KEY);
let injectedLocale: string | undefined;
if (localeDataInjector) {
const injectLocaleData = localeDataInjector.get();
if (injectLocaleData) {
injectedLocale = injectLocaleData.locale;
}
}
locale = locale || i18nCore.locale;
const localeMessages = getCachedMessages(bundle, locale);

if (localeMessages) {
return localeMessages;
if (next.locale && current.locale !== next.locale) {
const result = setLocale({ locale: next.locale, local: true });
if (isThenable(result)) {
result.then(() => {
invalidator();
});
return current.locale || injectedLocale;
}
}

i18nCore(bundle, locale).then(() => {
if (current.locale !== next.locale) {
invalidator();
});
}

function resolveBundle<T extends Messages>(bundle: Bundle<T>): Bundle<T> {
let { i18nBundle } = properties();
if (i18nBundle) {
if (i18nBundle instanceof Map) {
i18nBundle = i18nBundle.get(bundle);

if (!i18nBundle) {
return bundle;
}
}

return i18nBundle as Bundle<T>;
}
return bundle;
}
return next.locale || injectedLocale;
});

function getBlankMessages<T extends Messages>(bundle: Bundle<T>): T {
const blank = {} as Messages;
return Object.keys(bundle.messages).reduce((blank, key) => {
blank[key] = '';
return blank;
}, blank) as T;
}
injector.subscribe(INJECTOR_KEY);

return {
localize<T extends Messages>(bundle: Bundle<T>, useDefaults = false): LocalizedMessages<T> {
let { locale } = properties();
bundle = resolveBundle(bundle);
const messages = getLocaleMessages(bundle);
const isPlaceholder = !messages;
if (!locale) {
const injectedLocale = injector.get<Injector<LocaleData>>(INJECTOR_KEY);
if (injectedLocale) {
locale = injectedLocale.get().locale;
localize<T extends Messages>(bundle: Bundle<T>): LocalizedMessages<T> {
let { locale = getCurrentLocale(), i18nBundle } = properties();
if (i18nBundle) {
if (i18nBundle instanceof Map) {
bundle = i18nBundle.get(bundle) || bundle;
} else {
bundle = i18nBundle as Bundle<T>;
}
}

const format =
isPlaceholder && !useDefaults
? () => ''
: (key: keyof T, options?: any) => formatMessage(bundle, key as string, options, locale);

return Object.create({
format,
isPlaceholder,
messages: messages || (useDefaults ? bundle.messages : getBlankMessages(bundle))
});
return localizeBundle(bundle, { locale, invalidator });
},
set(localeData?: LocaleData) {
const currentLocale = injector.get<Injector<LocaleData | undefined>>(INJECTOR_KEY);
if (currentLocale) {
currentLocale.set(localeData);
const localeDataInjector = injector.get<Injector<LocaleData | undefined>>(INJECTOR_KEY);
if (localeDataInjector) {
if (localeData && localeData.locale) {
const result = setLocale({ locale: localeData.locale });
if (isThenable(result)) {
result.then(() => {
localeDataInjector.set(localeData);
});
return;
}
}
localeDataInjector.set(localeData);
}
},
get() {
const currentLocale = injector.get<Injector<LocaleData | undefined>>(INJECTOR_KEY);
if (currentLocale) {
return currentLocale.get();
const localeDataInjector = injector.get<Injector<LocaleData | undefined>>(INJECTOR_KEY);
if (localeDataInjector) {
return localeDataInjector.get();
}
}
};
Expand Down
Loading

0 comments on commit 6224e88

Please sign in to comment.