Dynamically fetch language bundles at runtime.

This commit is contained in:
Mike Cao 2021-06-29 18:41:34 -07:00
parent 73e83ad767
commit f91cc82c82
20 changed files with 95 additions and 132 deletions

1
.gitignore vendored
View File

@ -17,6 +17,7 @@
/build
/public/umami.js
/public/geo
/public/lang
/lang-compiled
# misc

View File

@ -27,7 +27,7 @@ import styles from './Calendar.module.css';
import Icon from './Icon';
export default function Calendar({ date, minDate, maxDate, onChange }) {
const [locale] = useLocale();
const { locale } = useLocale();
const [selectMonth, setSelectMonth] = useState(false);
const [selectYear, setSelectYear] = useState(false);

View File

@ -55,7 +55,7 @@ const filterOptions = [
];
function DateFilter({ value, startDate, endDate, onChange, className }) {
const [locale] = useLocale();
const { locale } = useLocale();
const [showPicker, setShowPicker] = useState(false);
const displayValue =
value === 'custom' ? (
@ -102,7 +102,7 @@ function DateFilter({ value, startDate, endDate, onChange, className }) {
}
const CustomRange = ({ startDate, endDate, onClick }) => {
const [locale] = useLocale();
const { locale } = useLocale();
function handleClick(e) {
e.stopPropagation();

View File

@ -12,7 +12,7 @@ import useLocale from 'hooks/useLocale';
function RefreshButton({ websiteId }) {
const dispatch = useDispatch();
const [locale] = useLocale();
const { locale } = useLocale();
const [dateRange] = useDateRange(websiteId);
const [loading, setLoading] = useState(false);
const completed = useSelector(state => state.queries[`/api/website/${websiteId}/stats`]);

View File

@ -24,7 +24,7 @@ function WorldMap({ data, className }) {
}),
[theme],
);
const [locale] = useLocale();
const { locale } = useLocale();
const countryNames = useCountryNames(locale);
function getFillColor(code) {

View File

@ -9,7 +9,7 @@ import { rtlLocales } from 'lib/lang';
export default function Footer() {
const { current } = useVersion();
const [locale] = useLocale();
const { locale } = useLocale();
return (
<footer className="container" dir={rtlLocales.includes(locale) ? 'rtl' : 'ltr'}>

View File

@ -19,7 +19,7 @@ import Bars from 'assets/bars.svg';
export default function Header() {
const user = useSelector(state => state.user);
const [active, setActive] = useState(false);
const [locale] = useLocale();
const { locale } = useLocale();
function handleClick() {
setActive(state => !state);

View File

@ -6,7 +6,7 @@ import useLocale from 'hooks/useLocale';
import { rtlLocales } from 'lib/lang';
export default function Layout({ title, children, header = true, footer = true }) {
const [locale] = useLocale();
const { locale } = useLocale();
const dir = rtlLocales.includes(locale) ? 'rtl' : 'ltr';
return (

View File

@ -27,7 +27,7 @@ export default function BarChart({
const canvas = useRef();
const chart = useRef();
const [tooltip, setTooltip] = useState(null);
const [locale] = useLocale();
const { locale } = useLocale();
const [theme] = useTheme();
const forceUpdate = useForceUpdate();

View File

@ -6,7 +6,7 @@ import useCountryNames from 'hooks/useCountryNames';
import useLocale from 'hooks/useLocale';
export default function CountriesTable({ websiteId, onDataLoad, ...props }) {
const [locale] = useLocale();
const { locale } = useLocale();
const countryNames = useCountryNames(locale);
function renderLabel({ x }) {

View File

@ -6,7 +6,7 @@ import styles from './Legend.module.css';
import useForceUpdate from '../../hooks/useForceUpdate';
export default function Legend({ chart }) {
const [locale] = useLocale();
const { locale } = useLocale();
const forceUpdate = useForceUpdate();
function handleClick(index) {

View File

@ -31,7 +31,7 @@ const TYPE_ICONS = {
export default function RealtimeLog({ data, websites, websiteId }) {
const intl = useIntl();
const [locale] = useLocale();
const { locale } = useLocale();
const countryNames = useCountryNames(locale);
const [filter, setFilter] = useState(TYPE_ALL);

View File

@ -29,7 +29,7 @@ function filterWebsite(data, id) {
}
export default function RealtimeDashboard() {
const [locale] = useLocale();
const { locale } = useLocale();
const countryNames = useCountryNames(locale);
const [data, setData] = useState();
const [websiteId, setWebsiteId] = useState(0);

View File

@ -9,7 +9,7 @@ import styles from './DateRangeSetting.module.css';
import useLocale from 'hooks/useLocale';
export default function DateRangeSetting() {
const [locale] = useLocale();
const { locale } = useLocale();
const [dateRange, setDateRange] = useDateRange();
const { startDate, endDate, value } = dateRange;

View File

@ -1,15 +1,16 @@
import React from 'react';
import { menuOptions } from 'lib/lang';
import { languages } from 'lib/lang';
import useLocale from 'hooks/useLocale';
import MenuButton from 'components/common/MenuButton';
import Globe from 'assets/globe.svg';
import styles from './LanguageButton.module.css';
export default function LanguageButton() {
const [locale, setLocale] = useLocale();
const { locale, saveLocale } = useLocale();
const menuOptions = Object.keys(languages).map(key => ({ ...languages[key], value: key }));
function handleSelect(value) {
setLocale(value);
saveLocale(value);
}
switch (locale) {

View File

@ -9,7 +9,7 @@ import useLocale from './useLocale';
export default function useDateRange(websiteId, defaultDateRange = DEFAULT_DATE_RANGE) {
const dispatch = useDispatch();
const [locale] = useLocale();
const { locale } = useLocale();
const dateRange = useSelector(state => state.websites[websiteId]?.dateRange);
const forceUpdate = useForceUpdate();

View File

@ -1,16 +1,49 @@
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { setLocale } from 'redux/actions/app';
import { setItem } from 'lib/web';
import { useRouter } from 'next/router';
import { get, setItem } from 'lib/web';
import { LOCALE_CONFIG } from 'lib/constants';
import useForceUpdate from 'hooks/useForceUpdate';
import enUS from 'public/lang/en-US.json';
const messages = {
'en-US': enUS,
};
export default function useLocale() {
const locale = useSelector(state => state.app.locale);
const dispatch = useDispatch();
const { basePath } = useRouter();
const forceUpdate = useForceUpdate();
function saveLocale(value) {
setItem(LOCALE_CONFIG, value);
dispatch(setLocale(value));
async function loadMessages(locale) {
const { ok, data } = await get(`${basePath}/lang/${locale}.json`);
if (ok) {
messages[locale] = data;
}
}
return [locale, saveLocale];
async function saveLocale(value) {
if (!messages[value]) {
await loadMessages(value);
}
setItem(LOCALE_CONFIG, value);
if (locale !== value) {
dispatch(setLocale(value));
} else {
forceUpdate();
}
}
useEffect(() => {
if (!messages[locale]) {
saveLocale(locale);
}
}, [locale]);
return { locale, saveLocale, messages };
}

View File

@ -33,76 +33,42 @@ import {
ca,
hu,
} from 'date-fns/locale';
import arSAMessages from 'lang-compiled/ar-SA.json';
import enMessages from 'lang-compiled/en-US.json';
import nlMessages from 'lang-compiled/nl-NL.json';
import zhCNMessages from 'lang-compiled/zh-CN.json';
import zhTWMessages from 'lang-compiled/zh-TW.json';
import trTRMessages from 'lang-compiled/tr-TR.json';
import ruRUMessages from 'lang-compiled/ru-RU.json';
import deDEMessages from 'lang-compiled/de-DE.json';
import jaMessages from 'lang-compiled/ja-JP.json';
import esMXMessages from 'lang-compiled/es-MX.json';
import frMessages from 'lang-compiled/fr-FR.json';
import mnMNMessages from 'lang-compiled/mn-MN.json';
import daMessages from 'lang-compiled/da-DK.json';
import svMessages from 'lang-compiled/sv-SE.json';
import grMessages from 'lang-compiled/el-GR.json';
import foMessages from 'lang-compiled/fo-FO.json';
import ptMessages from 'lang-compiled/pt-PT.json';
import ptBRMessages from 'lang-compiled/pt-BR.json';
import roMessages from 'lang-compiled/ro-RO.json';
import nbNOMessages from 'lang-compiled/nb-NO.json';
import idMessages from 'lang-compiled/id-ID.json';
import ukMessages from 'lang-compiled/uk-UA.json';
import fiMessages from 'lang-compiled/fi-FI.json';
import csMessages from 'lang-compiled/cs-CZ.json';
import skMessages from 'lang-compiled/sk-SK.json';
import plMessages from 'lang-compiled/pl-PL.json';
import taMessages from 'lang-compiled/ta-IN.json';
import hiMessages from 'lang-compiled/hi-IN.json';
import heMessages from 'lang-compiled/he-IL.json';
import itMessages from 'lang-compiled/it-IT.json';
import faIRMessages from 'lang-compiled/fa-IR.json';
import msMYMessages from 'lang-compiled/ms-MY.json';
import caMessages from 'lang-compiled/ca-ES.json';
import huMessages from 'lang-compiled/hu-HU.json';
export const messages = {
'ar-SA': arSAMessages,
'en-US': enMessages,
'nl-NL': nlMessages,
'zh-CN': zhCNMessages,
'zh-TW': zhTWMessages,
'de-DE': deDEMessages,
'ru-RU': ruRUMessages,
'tr-TR': trTRMessages,
'ja-JP': jaMessages,
'es-MX': esMXMessages,
'fr-FR': frMessages,
'mn-MN': mnMNMessages,
'da-DK': daMessages,
'sv-SE': svMessages,
'el-GR': grMessages,
'fo-FO': foMessages,
'pt-PT': ptMessages,
'pt-BR': ptBRMessages,
'ro-RO': roMessages,
'nb-NO': nbNOMessages,
'id-ID': idMessages,
'uk-UA': ukMessages,
'fi-FI': fiMessages,
'cs-CZ': csMessages,
'sk-SK': skMessages,
'pl-PL': plMessages,
'ta-IN': taMessages,
'hi-IN': hiMessages,
'he-IL': heMessages,
'it-IT': itMessages,
'fa-IR': faIRMessages,
'ms-MY': msMYMessages,
'ca-ES': caMessages,
'hu-HU': huMessages,
export const languages = {
'ar-SA': { label: 'العربية', display: 'ar' },
'zh-CN': { label: '中文', display: 'cn' },
'zh-TW': { label: '中文(繁體)', display: 'tw' },
'ca-ES': { label: 'Català', display: 'ca' },
'cs-CZ': { label: 'Čeština', display: 'cs' },
'da-DK': { label: 'Dansk', display: 'da' },
'de-DE': { label: 'Deutsch', display: 'de' },
'en-US': { label: 'English', display: 'en' },
'es-MX': { label: 'Español', display: 'es' },
'fa-IR': { label: 'فارسی', display: 'fa' },
'fo-FO': { label: 'Føroyskt', display: 'fo' },
'fr-FR': { label: 'Français', display: 'fr' },
'el-GR': { label: 'Ελληνικά', display: 'el' },
'he-IL': { label: 'עברית', display: 'he' },
'hi-IN': { label: 'हिन्दी', display: 'hi' },
'hu-HU': { label: 'Hungarian', display: 'hu' },
'it-IT': { label: 'Italiano', display: 'it' },
'id-ID': { label: 'Bahasa Indonesia', display: 'id' },
'ja-JP': { label: '日本語', display: 'ja' },
'ms-MY': { label: 'Malay', display: 'ms' },
'mn-MN': { label: 'Монгол', display: 'mn' },
'nl-NL': { label: 'Nederlands', display: 'nl' },
'nb-NO': { label: 'Norsk Bokmål', display: 'nb' },
'pl-PL': { label: 'Polski', display: 'pl' },
'pt-PT': { label: 'Português', display: 'pt' },
'pt-BR': { label: 'Português do Brasil', display: 'pt-BR' },
'ru-RU': { label: 'Русский', display: 'ru' },
'ro-RO': { label: 'Română', display: 'ro' },
'sk-SK': { label: 'Slovenčina', display: 'sk' },
'fi-FI': { label: 'Suomi', display: 'fi' },
'sv-SE': { label: 'Svenska', display: 'sv' },
'ta-IN': { label: 'தமிழ்', display: 'ta' },
'tr-TR': { label: 'Türkçe', display: 'tr' },
'uk-UA': { label: 'українська', display: 'uk' },
};
export const rtlLocales = ['ar-SA', 'fa-IR'];
@ -143,40 +109,3 @@ export const dateLocales = {
'ca-ES': ca,
'hu-HU': hu,
};
export const menuOptions = [
{ label: 'العربية', value: 'ar-SA', display: 'ar' },
{ label: '中文', value: 'zh-CN', display: 'cn' },
{ label: '中文(繁體)', value: 'zh-TW', display: 'tw' },
{ label: 'Català', value: 'ca-ES', display: 'ca' },
{ label: 'Čeština', value: 'cs-CZ', display: 'cs' },
{ label: 'Dansk', value: 'da-DK', display: 'da' },
{ label: 'Deutsch', value: 'de-DE', display: 'de' },
{ label: 'English', value: 'en-US', display: 'en' },
{ label: 'Español', value: 'es-MX', display: 'es' },
{ label: 'فارسی', value: 'fa-IR', display: 'fa' },
{ label: 'Føroyskt', value: 'fo-FO', display: 'fo' },
{ label: 'Français', value: 'fr-FR', display: 'fr' },
{ label: 'Ελληνικά', value: 'el-GR', display: 'el' },
{ label: 'עברית', value: 'he-IL', display: 'he' },
{ label: 'हिन्दी', value: 'hi-IN', display: 'hi' },
{ label: 'Hungarian', value: 'hu-HU', display: 'hu' },
{ label: 'Italiano', value: 'it-IT', display: 'it' },
{ label: 'Bahasa Indonesia', value: 'id-ID', display: 'id' },
{ label: '日本語', value: 'ja-JP', display: 'ja' },
{ label: 'Malay', value: 'ms-MY', display: 'ms' },
{ label: 'Монгол', value: 'mn-MN', display: 'mn' },
{ label: 'Nederlands', value: 'nl-NL', display: 'nl' },
{ label: 'Norsk Bokmål', value: 'nb-NO', display: 'nb' },
{ label: 'Polski', value: 'pl-PL', display: 'pl' },
{ label: 'Português', value: 'pt-PT', display: 'pt' },
{ label: 'Português do Brasil', value: 'pt-BR', display: 'pt-BR' },
{ label: 'Русский', value: 'ru-RU', display: 'ru' },
{ label: 'Română', value: 'ro-RO', display: 'ro' },
{ label: 'Slovenčina', value: 'sk-SK', display: 'sk' },
{ label: 'Suomi', value: 'fi-FI', display: 'fi' },
{ label: 'Svenska', value: 'sv-SE', display: 'sv' },
{ label: 'தமிழ்', value: 'ta-IN', display: 'ta' },
{ label: 'Türkçe', value: 'tr-TR', display: 'tr' },
{ label: 'українська', value: 'uk-UA', display: 'uk' },
];

View File

@ -30,7 +30,7 @@
"extract-lang": "formatjs extract '{pages,components}/**/*.js' --out-file build/messages.json",
"merge-lang": "node scripts/merge-lang.js",
"format-lang": "node scripts/format-lang.js",
"compile-lang": "formatjs compile-folder --ast build lang-compiled",
"compile-lang": "formatjs compile-folder --ast build public/lang",
"check-lang": "node scripts/check-lang.js",
"download-country-names": "node scripts/download-country-names.js",
"loadtest": "node scripts/loadtest.js",

View File

@ -6,7 +6,6 @@ import { Provider } from 'react-redux';
import { useStore } from 'redux/store';
import useLocale from 'hooks/useLocale';
import useForceSSL from 'hooks/useForceSSL';
import { messages } from 'lib/lang';
import 'styles/variables.css';
import 'styles/bootstrap-grid.css';
import 'styles/index.css';
@ -14,7 +13,7 @@ import '@fontsource/inter/400.css';
import '@fontsource/inter/600.css';
const Intl = ({ children }) => {
const [locale] = useLocale();
const { locale, messages } = useLocale();
const Wrapper = ({ children }) => <span className={locale}>{children}</span>;