From 67b67b28b229fca0bd937d154270c6a6312e2bce Mon Sep 17 00:00:00 2001 From: Long Ho Date: Wed, 16 Sep 2020 19:01:21 -0400 Subject: [PATCH] feat(examples/with-react-intl): add locale negotation to client side (#16806) fix #16752 cc @thuringia --- examples/with-react-intl/lang/en-GB.json | 7 ++ examples/with-react-intl/lang/zh-Hans-CN.json | 7 ++ examples/with-react-intl/lang/zh-Hant-HK.json | 7 ++ examples/with-react-intl/package.json | 4 +- examples/with-react-intl/pages/_app.tsx | 66 ++++++++++++++----- examples/with-react-intl/pages/_document.tsx | 24 ++++--- 6 files changed, 91 insertions(+), 24 deletions(-) create mode 100644 examples/with-react-intl/lang/en-GB.json create mode 100644 examples/with-react-intl/lang/zh-Hans-CN.json create mode 100644 examples/with-react-intl/lang/zh-Hant-HK.json diff --git a/examples/with-react-intl/lang/en-GB.json b/examples/with-react-intl/lang/en-GB.json new file mode 100644 index 0000000000000..05cc13de4e0b2 --- /dev/null +++ b/examples/with-react-intl/lang/en-GB.json @@ -0,0 +1,7 @@ +{ + "AvQcw8": "React Intl Next.js Example, in en-GB", + "N015Sp": "Hello, World, from the UK!", + "ejEGdx": "Home (British version)", + "fnfXnF": "An example app integrating React Intl with Next.js", + "g5pX+a": "About (British version)" +} diff --git a/examples/with-react-intl/lang/zh-Hans-CN.json b/examples/with-react-intl/lang/zh-Hans-CN.json new file mode 100644 index 0000000000000..b229ec00c6222 --- /dev/null +++ b/examples/with-react-intl/lang/zh-Hans-CN.json @@ -0,0 +1,7 @@ +{ + "AvQcw8": " React Intl Next.js示例", + "N015Sp": "你好,世界!", + "ejEGdx": "首页", + "fnfXnF": "一个将React Intl与Next.js集成的示例应用程序", + "g5pX+a": "关于" +} diff --git a/examples/with-react-intl/lang/zh-Hant-HK.json b/examples/with-react-intl/lang/zh-Hant-HK.json new file mode 100644 index 0000000000000..154b84d27ba04 --- /dev/null +++ b/examples/with-react-intl/lang/zh-Hant-HK.json @@ -0,0 +1,7 @@ +{ + "AvQcw8": "React Intl Next.js示例", + "N015Sp": "你好,世界!", + "ejEGdx": "首頁", + "fnfXnF": "一個將React Intl與Next.js集成的示例應用程序", + "g5pX+a": "關於" +} diff --git a/examples/with-react-intl/package.json b/examples/with-react-intl/package.json index 2e41e4fcaa292..eeaee257d9a55 100644 --- a/examples/with-react-intl/package.json +++ b/examples/with-react-intl/package.json @@ -3,10 +3,12 @@ "version": "1.0.0", "scripts": { "dev": "cross-env NODE_ICU_DATA=node_modules/full-icu ts-node --project tsconfig.server.json server.ts", + "dev-no-custom-server": "next dev", "build": "npm run extract:i18n && npm run compile:i18n && next build && tsc -p tsconfig.server.json", "extract:i18n": "formatjs extract '{pages,components}/*.{js,ts,tsx}' --format simple --id-interpolation-pattern '[sha512:contenthash:base64:6]' --out-file lang/en.json", "compile:i18n": "formatjs compile-folder --ast --format simple lang/ compiled-lang/", - "start": "cross-env NODE_ENV=production NODE_ICU_DATA=node_modules/full-icu node dist/server" + "start": "cross-env NODE_ENV=production NODE_ICU_DATA=node_modules/full-icu node dist/server", + "start-no-custom-server": "next start" }, "dependencies": { "@formatjs/cli": "^2.7.3", diff --git a/examples/with-react-intl/pages/_app.tsx b/examples/with-react-intl/pages/_app.tsx index 4f184ca0ee468..75d209e5f7a5e 100644 --- a/examples/with-react-intl/pages/_app.tsx +++ b/examples/with-react-intl/pages/_app.tsx @@ -5,37 +5,73 @@ import App from 'next/app'; function MyApp({Component, pageProps, locale, messages}) { return ( - + ); } -// We need to load and expose the translations on the request for the user's -// locale. These will only be used in production, in dev the `defaultMessage` in -// each message description in the source code will be used. -const getMessages = (locale: string = 'en') => { - switch (locale) { - default: - return import('../compiled-lang/en.json'); - case 'fr': - return import('../compiled-lang/fr.json'); +/** + * Get the messages and also do locale negotiation. A multi-lingual user + * can specify locale prefs like ['ja', 'en-GB', 'en'] which is interpreted as + * Japanese, then British English, then English + * @param locales list of requested locales + * @returns {[string, Promise]} A tuple containing the negotiated locale + * and the promise of fetching the translated messages + */ +function getMessages(locales: string | string[] = ['en']) { + if (!Array.isArray(locales)) { + locales = [locales]; } -}; + let langBundle; + let locale; + for (let i = 0; i < locales.length && !locale; i++) { + locale = locales[i]; + switch (locale) { + case 'fr': + langBundle = import('../compiled-lang/fr.json'); + break; + case 'en-GB': + langBundle = import('../compiled-lang/en-GB.json'); + break; + case 'zh-Hans-CN': + langBundle = import('../compiled-lang/zh-Hans-CN.json'); + break; + case 'zh-Hant-HK': + langBundle = import('../compiled-lang/zh-Hant-HK.json'); + break; + default: + break; + // Add more languages + } + } + if (!langBundle) { + return ['en', import('../compiled-lang/en.json')]; + } + return [locale, langBundle]; +} const getInitialProps: typeof App.getInitialProps = async appContext => { const { ctx: {req}, } = appContext; - const locale = (req as any)?.locale || (window as any).LOCALE || 'en'; + const requestedLocales: string | string[] = + (req as any)?.locale || + (typeof navigator !== 'undefined' && navigator.languages) || + // IE11 + (typeof navigator !== 'undefined' && (navigator as any).userLanguage) || + (typeof window !== 'undefined' && (window as any).LOCALE) || + 'en'; + + const [supportedLocale, messagePromise] = getMessages(requestedLocales); const [appProps, messages] = await Promise.all([ - polyfill(locale), - getMessages(locale), + polyfill(supportedLocale), + messagePromise, App.getInitialProps(appContext), ]); - return {...(appProps as any), locale, messages}; + return {...(appProps as any), locale: supportedLocale, messages}; }; MyApp.getInitialProps = getInitialProps; diff --git a/examples/with-react-intl/pages/_document.tsx b/examples/with-react-intl/pages/_document.tsx index 0eda159f21413..e183ecc9d333c 100644 --- a/examples/with-react-intl/pages/_document.tsx +++ b/examples/with-react-intl/pages/_document.tsx @@ -16,25 +16,33 @@ class MyDocument extends Document { static async getInitialProps(ctx: DocumentContext) { const {req} = ctx; const initialProps = await Document.getInitialProps(ctx); + const locale = (req as any).locale; return { ...initialProps, - locale: (req as any).locale || 'en', - lang: ((req as any).locale || 'en').split('-')[0], + locale, + lang: locale ? locale.split('-')[0] : undefined, nonce: (req as any).nonce, }; } render() { + let scriptEl; + if (this.props.locale) { + scriptEl = ( + + ); + } + return ( - + {scriptEl}