mirror of
https://github.com/wassname/talk.git
synced 2026-07-14 11:18:50 +08:00
[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:
@@ -0,0 +1,25 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { LanguageCode, LOCALES_MAP } from "coral-common/helpers/i18n";
|
||||
import { PropTypesOf } from "coral-framework/types";
|
||||
import { Option, SelectField } from "coral-ui/components";
|
||||
|
||||
interface Props extends PropTypesOf<typeof SelectField> {
|
||||
value: LanguageCode;
|
||||
}
|
||||
|
||||
const LocaleField: FunctionComponent<Props> = props => {
|
||||
return (
|
||||
<SelectField {...props}>
|
||||
{Object.keys(LOCALES_MAP).map((lang: LanguageCode) => {
|
||||
return (
|
||||
<Option value={lang} key={lang}>
|
||||
{LOCALES_MAP[lang]}
|
||||
</Option>
|
||||
);
|
||||
})}
|
||||
</SelectField>
|
||||
);
|
||||
};
|
||||
|
||||
export default LocaleField;
|
||||
@@ -6,3 +6,4 @@ export { default as OIDCButton } from "./OIDCButton";
|
||||
export { default as Markdown } from "./Markdown";
|
||||
export { default as FadeInTransition } from "./FadeInTransition";
|
||||
export { default as DurationField, DURATION_UNIT } from "./DurationField";
|
||||
export { default as LocaleField } from "./LocaleField";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { LanguageCode } from "coral-common/helpers/i18n";
|
||||
|
||||
import { RestClient } from "../lib/rest";
|
||||
|
||||
export interface InstallInput {
|
||||
@@ -8,6 +10,7 @@ export interface InstallInput {
|
||||
url: string;
|
||||
};
|
||||
allowedDomains: string[];
|
||||
locale: LanguageCode;
|
||||
};
|
||||
user: {
|
||||
username: string;
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
GQLCOMMENT_FLAG_REPORTED_REASON,
|
||||
GQLCOMMENT_SORT,
|
||||
GQLCOMMENT_STATUS,
|
||||
GQLLOCALES,
|
||||
GQLMODERATION_MODE,
|
||||
GQLMODERATION_QUEUE,
|
||||
GQLSTORY_STATUS,
|
||||
@@ -35,7 +34,6 @@ export type GQLCOMMENT_FLAG_REPORTED_REASON_RL = RelayEnumLiteral<
|
||||
>;
|
||||
export type GQLCOMMENT_SORT_RL = RelayEnumLiteral<typeof GQLCOMMENT_SORT>;
|
||||
export type GQLCOMMENT_STATUS_RL = RelayEnumLiteral<typeof GQLCOMMENT_STATUS>;
|
||||
export type GQLLOCALES_RL = RelayEnumLiteral<typeof GQLLOCALES>;
|
||||
export type GQLMODERATION_MODE_RL = RelayEnumLiteral<typeof GQLMODERATION_MODE>;
|
||||
export type GQLSTORY_STATUS_RL = RelayEnumLiteral<typeof GQLSTORY_STATUS>;
|
||||
export type GQLUSER_AUTH_CONDITIONS_RL = RelayEnumLiteral<
|
||||
|
||||
@@ -104,6 +104,7 @@ export default function createTestRenderer<
|
||||
uuidGenerator: createUUIDGenerator(),
|
||||
eventEmitter: new EventEmitter2({ wildcard: true, maxListeners: 20 }),
|
||||
clearSession: sinon.stub(),
|
||||
changeLocale: sinon.stub(),
|
||||
transitionControl: {
|
||||
allowTransition: true,
|
||||
history: [],
|
||||
|
||||
Reference in New Issue
Block a user