Skip to content

Commit

Permalink
dynamically load language files
Browse files Browse the repository at this point in the history
also downgrade babel-eslint because it fails to parse syntax
see: babel/babel-eslint#815
  • Loading branch information
juliaqiuxy authored and jmurzy committed May 22, 2020
1 parent abd8b77 commit d4c059a
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 85 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -58,7 +58,7 @@
"@babel/cli": "^7.8.4",
"@babel/node": "^7.8.7",
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
"babel-eslint": "^10.1.0",
"babel-eslint": "^8.2.6",
"babel-plugin-inline-react-svg": "^1.1.1",
"babel-plugin-styled-components": "^1.10.7",
"dotenv": "^8.2.0",
Expand Down
68 changes: 39 additions & 29 deletions src/lib/useLocale.js
@@ -1,6 +1,6 @@
import React, {
Fragment,
useEffect,
useRef,
useState,
useCallback,
createContext,
Expand All @@ -14,26 +14,20 @@ import {
} from './cookies';
import parseLocaleParts from '../utils/parseLocaleParts';

// TODO dynamically load these in runtime instead of bundling up every lang
import en from '../langs/en.json';
import es from '../langs/es.json';
import zh from '../langs/zh.json';

const DEFAULT_LOCALE_PARTS = parseLocaleParts('en');

const MESSAGES = {
const SUPPORTED_LOCALES_TO_LANG = {
// English
en,
en: 'en',
// Spanish
es,
'es-ES': es,
'es-US': es,
'es-MX': es,
es: 'es',
'es-ES': 'es',
'es-US': 'es',
'es-MX': 'es',
// Chinese
zh,
zh: 'zh',
};
const SUPPORTED_LOCALES = Object.keys(SUPPORTED_LOCALES_TO_LANG);

const SUPPORTED_LOCALES = Object.keys(MESSAGES);
const DEFAULT_LOCALE_PARTS = parseLocaleParts('en');

const context = createContext();

Expand All @@ -53,35 +47,51 @@ export const pickSupportedLocale = (requestedLocale, supportedLocales = SUPPORTE
return DEFAULT_LOCALE_PARTS.language;
};

export const loadMessages = async (language) => {
const messages = await import(`../langs/${language}.json`).then((mod) => mod.default);
return messages;
};

export const IntlProvider = ({ children, ...props }) => {
const [preferredLocale, setPreferredLocale] = useState(
() => props.preferredLocale,
);
const messagesCache = useRef({
[props.locale]: props.initialMessages,
});

const setPreferredLocaleWithSideEffects = useCallback((preference) => {
const [preferredLocale, setPreferredLocale] = useState(props.preferredLocale);
const [locale, setLocale] = useState(props.locale);

const setPreferredLocaleWithSideEffects = useCallback(async (preference) => {
if (preference === preferredLocale) {
return;
}

if (preference) {
setCookie(null, 'locale', preference, BASE_COOKIE_OPTIONS);
} else {
destroyCookie(null, 'locale', BASE_COOKIE_OPTIONS);
}

setPreferredLocale(preference);
}, [preferredLocale]);

// install new locale
const newLocale = pickSupportedLocale(preference || props.systemLocale);

const { language, dir } = parseLocaleParts(newLocale);
document.documentElement.lang = language;
document.documentElement.dir = dir;

const messages = await loadMessages(language);
messagesCache.current[language] = messages;

setLocale(newLocale);
}, [preferredLocale, props.systemLocale]);

const contextValue = useMemo(() => [
preferredLocale, setPreferredLocaleWithSideEffects,
], [preferredLocale, setPreferredLocaleWithSideEffects]);

const locale = pickSupportedLocale(preferredLocale || props.systemLocale);

useEffect(() => {
const { language, dir } = parseLocaleParts(locale);
document.documentElement.lang = language;
document.documentElement.dir = dir;
}, [locale]);
const { language } = parseLocaleParts(locale);
const messages = messagesCache.current[language];

return (
<context.Provider value={contextValue}>
Expand All @@ -90,7 +100,7 @@ export const IntlProvider = ({ children, ...props }) => {
{...props}
locale={locale}
defaultLocale={DEFAULT_LOCALE_PARTS.language}
messages={MESSAGES[locale]}
messages={messages}
textComponent={Fragment}
>
{children}
Expand Down
4 changes: 2 additions & 2 deletions src/lib/useTypewriter.js
Expand Up @@ -60,7 +60,7 @@ const useTypewriter = () => {
}, []);

const renderSequence = useCallback(async (ngrams, abortSignal) => {
// eslint-disable-next-line no-restricted-syntax
// eslint-disable-next-line no-restricted-syntax, no-unused-vars
for (const ngram of ngrams) {
if (abortSignal.aborted) {
break;
Expand All @@ -87,7 +87,7 @@ const useTypewriter = () => {
return;
}

// eslint-disable-next-line no-restricted-syntax
// eslint-disable-next-line no-restricted-syntax, no-unused-vars
for (const item of items) {
let ngrams = generateTypeSequence(item, TYPE_PAUSE);
// eslint-disable-next-line no-await-in-loop
Expand Down
26 changes: 23 additions & 3 deletions src/pages/_app.js
Expand Up @@ -12,10 +12,16 @@ import ErrorView from '../components/ErrorView/ErrorView';

import { EntityNotFoundError, APIError } from '../api';

import { IntlProvider, getLocalePreference } from '../lib/useLocale';
import {
IntlProvider,
getLocalePreference,
pickSupportedLocale,
loadMessages,
} from '../lib/useLocale';
import { ThemeProvider, getThemePreference } from '../lib/useTheme';

import getSystemLocale from '../utils/getSystemLocale';
import parseLocaleParts from '../utils/parseLocaleParts';

import '../css/nprogress.css';

Expand Down Expand Up @@ -200,16 +206,26 @@ class App extends NextApp {
}

const preferredTheme = getThemePreference(ctx);

const preferredLocale = getLocalePreference(ctx);
const systemLocale = getSystemLocale(ctx);

// Given a user preferred locale and user's system locale determine the
// locale to render based what we support
const locale = pickSupportedLocale(preferredLocale || systemLocale);
const { language } = parseLocaleParts(locale);
// Initial messages are needed to rehydrate them on the client
const initialMessages = await loadMessages(language);

const ssrNow = Date.now();

// TODO infer this value since Server TZ (UTC) !== Client TZ and it'd break ssr
const { timeZone } = Intl.DateTimeFormat().resolvedOptions();

return {
errorPageProps,
initialMessages,
locale,
pageProps,
preferredLocale,
preferredTheme,
Expand All @@ -223,11 +239,13 @@ class App extends NextApp {
const {
Component,
errorPageProps,
preferredLocale,
initialMessages,
locale,
pageProps,
preferredLocale,
preferredTheme,
ssrNow,
systemLocale,
preferredTheme,
timeZone,
} = this.props;

Expand All @@ -237,6 +255,8 @@ class App extends NextApp {
<GlobalStyle />

<IntlProvider
initialMessages={initialMessages}
locale={locale}
preferredLocale={preferredLocale}
systemLocale={systemLocale}
timeZone={timeZone}
Expand Down
5 changes: 1 addition & 4 deletions src/pages/_document.js
Expand Up @@ -10,7 +10,6 @@ import getConfig from 'next/config';
import { ServerStyleSheet } from 'styled-components';

import { StaticHead } from '../components/Head/Head';
import { pickSupportedLocale } from '../lib/useLocale';
import parseLocaleParts from '../utils/parseLocaleParts';

const { publicRuntimeConfig: { GOOGLE_TAG_MANAGER_ID } } = getConfig();
Expand Down Expand Up @@ -55,13 +54,11 @@ class Document extends NextDocument {
render() {
// eslint-disable-next-line no-underscore-dangle
const {
systemLocale,
preferredLocale,
locale,
preferredTheme,
// eslint-disable-next-line no-underscore-dangle
} = this.props.__NEXT_DATA__.props;

const locale = pickSupportedLocale(preferredLocale || systemLocale);
const { language, dir } = parseLocaleParts(locale);

return (
Expand Down

0 comments on commit d4c059a

Please sign in to comment.