[CORL-406] Tenant Locale Selection (#2450)

* feat: added preload config

* feat: support changing locale

* fix: name case

* fix: removed unused code

* feat: added translations for default reactions

* fix: do not translate icon

* fix: shorter i18n keys
This commit is contained in:
Wyatt Johnson
2019-09-05 17:02:06 +00:00
committed by GitHub
parent 5bf4f22931
commit 04c56b3fb5
54 changed files with 673 additions and 207 deletions
@@ -7,6 +7,7 @@ import { MediaQueryMatchers } from "react-responsive";
import { Formatter } from "react-timeago";
import { Environment } from "relay-runtime";
import { LanguageCode } from "coral-common/helpers/i18n";
import { BrowserInfo } from "coral-framework/lib/browserInfo";
import { PostMessageService } from "coral-framework/lib/postMessage";
import { RestClient } from "coral-framework/lib/rest";
@@ -64,6 +65,9 @@ export interface CoralContext {
/** Clear session data. */
clearSession: (nextAccessToken?: string | null) => Promise<void>;
/** Change locale and rerender */
changeLocale: (locale: LanguageCode) => Promise<void>;
/** Controls router transitions (for tests) */
transitionControl?: TransitionControlData;
}
@@ -7,12 +7,14 @@ import { Formatter } from "react-timeago";
import { Environment, RecordSource, Store } from "relay-runtime";
import uuid from "uuid/v1";
import { LanguageCode } from "coral-common/helpers/i18n";
import { getBrowserInfo } from "coral-framework/lib/browserInfo";
import {
commitLocalUpdatePromisified,
LOCAL_ID,
setAccessTokenInLocalState,
} from "coral-framework/lib/relay";
import { RestClient } from "coral-framework/lib/rest";
import {
createLocalStorage,
createPromisifiedStorage,
@@ -20,11 +22,9 @@ import {
createSessionStorage,
PromisifiedStorage,
} from "coral-framework/lib/storage";
import { RestClient } from "coral-framework/lib/rest";
import { ClickFarAwayRegister } from "coral-ui/components/ClickOutside";
import { generateBundles, LocalesData, negotiateLanguages } from "../i18n";
import { generateBundles, LocalesData } from "../i18n";
import {
createManagedSubscriptionClient,
createNetwork,
@@ -41,9 +41,6 @@ export type InitLocalState = (
) => void | Promise<void>;
interface CreateContextArguments {
/** Locales that the user accepts, usually `navigator.languages`. */
userLocales: ReadonlyArray<string>;
/** Locales data that is returned by our `locales-loader`. */
localesData: LocalesData;
@@ -128,11 +125,12 @@ function createRestClient(tokenGetter: () => string, clientID: string) {
* Returns a managed CoralContextProvider, that includes given context
* and handles context changes, e.g. when a user session changes.
*/
function createMangedCoralContextProvider(
function createManagedCoralContextProvider(
context: CoralContext,
subscriptionClient: ManagedSubscriptionClient,
clientID: string,
initLocalState: InitLocalState
initLocalState: InitLocalState,
localesData: LocalesData
) {
const ManagedCoralContextProvider = class extends Component<
{},
@@ -144,6 +142,7 @@ function createMangedCoralContextProvider(
context: {
...context,
clearSession: this.clearSession,
changeLocale: this.changeLocale,
},
};
}
@@ -196,6 +195,25 @@ function createMangedCoralContextProvider(
);
};
// This is called when the locale should change.
private changeLocale = async (locale: LanguageCode) => {
// Add fallback locale.
const locales = [localesData.fallbackLocale];
if (locale && locale !== localesData.fallbackLocale) {
locales.splice(0, 0, locale);
}
const localeBundles = await generateBundles(locales, localesData);
const newContext = {
...this.state.context,
locales,
localeBundles,
};
// Propagate new context.
this.setState({
context: newContext,
});
};
public render() {
return (
<CoralContextProvider value={this.state.context}>
@@ -240,7 +258,6 @@ function resolveSessionStorage(pym?: PymChild): PromisifiedStorage {
*/
export default async function createManaged({
initLocalState = noop,
userLocales,
localesData,
pym,
eventEmitter = new EventEmitter2({ wildcard: true, maxListeners: 20 }),
@@ -261,11 +278,24 @@ export default async function createManaged({
}
// Initialize i18n.
const locales = negotiateLanguages(userLocales, localesData);
const locales = [localesData.fallbackLocale];
if (
document.documentElement.lang &&
document.documentElement.lang !== localesData.fallbackLocale
) {
// Use locale specified by the server.
locales.splice(0, 0, document.documentElement.lang);
} else if (
localesData.defaultLocale &&
localesData.defaultLocale !== localesData.fallbackLocale
) {
// Use default locale.
locales.splice(0, 0, localesData.defaultLocale);
}
if (process.env.NODE_ENV !== "production") {
// tslint:disable:next-line: no-console
console.log(`Negotiated locales ${JSON.stringify(locales)}`);
console.log(`Using locales ${JSON.stringify(locales)}`);
}
const localeBundles = await generateBundles(locales, localesData);
@@ -304,6 +334,9 @@ export default async function createManaged({
// Noop, this is later replaced by the
// managed CoralContextProvider.
clearSession: (nextAccessToken?: string | null) => Promise.resolve(),
// Noop, this is later replaced by the
// managed CoralContextProvider.
changeLocale: (locale?: LanguageCode) => Promise.resolve(),
};
// Initialize local state.
@@ -317,10 +350,11 @@ export default async function createManaged({
// Returns a managed CoralContextProvider, that includes the above
// context and handles context changes, e.g. when a user session changes.
return createMangedCoralContextProvider(
return createManagedCoralContextProvider(
context,
subscriptionClient,
clientID,
initLocalState
initLocalState,
localesData
);
}
@@ -36,8 +36,6 @@ if (process.env.NODE_ENV !== "production") {
* Given a locales array and the `data` from the `locales-loader`,
* generateMessages returns an Array of MessageContext as a Promise.
* This array is meant to be consumed by `react-fluent`.
*
* Use it in conjunction with `negotiateLanguages`.
*/
export default async function generateBundles(
locales: ReadonlyArray<string>,
@@ -1,5 +1,4 @@
export { default as generateBundles } from "./generateBundles";
export { default as negotiateLanguages } from "./negotiateLanguages";
export { BundledLocales, LoadableLocales, LocalesData } from "./locales";
export { default as getMessage } from "./getMessage";
export { default as withGetMessage, GetMessage } from "./withGetMessage";
@@ -1,27 +0,0 @@
import { negotiateLanguages as negotiate } from "fluent-langneg/compat";
import { LocalesData } from "./locales";
/**
* negotiateLanguages accepts `userLocales` which usually comes from
* `navigator.languages` and the locales `data` as generated by
* the `locales-loader` and returns an array of matching languages.
*/
export default function negotiateLanguages(
userLocales: ReadonlyArray<string>,
data: LocalesData
) {
// Choose locale that is best for the user.
const languages = negotiate(userLocales, data.availableLocales, {
defaultLocale: data.defaultLocale,
strategy: "lookup",
});
if (data.fallbackLocale && languages[0] !== data.fallbackLocale) {
// Use default locale as fallback in case we have
// missing keys.
languages.push(data.fallbackLocale);
}
return languages;
}