From 0a3fb70ec7a12898921dcde2ca98521b10fa9e08 Mon Sep 17 00:00:00 2001 From: Kostas Bariotis Date: Sat, 27 Jun 2020 12:02:54 +0100 Subject: [PATCH] Dynamicaly import locales (#1793) * dynamicly import locales * fix tests * reformat languages --- package.json | 2 +- src/components/InitializeApp.tsx | 24 +++++++++ src/components/LayerUI.tsx | 4 +- src/components/MobileMenu.tsx | 4 +- src/i18n.ts | 87 +++++++++++++++--------------- src/index.tsx | 5 +- src/tests/regressionTests.test.tsx | 12 +++-- 7 files changed, 85 insertions(+), 53 deletions(-) create mode 100644 src/components/InitializeApp.tsx diff --git a/package.json b/package.json index 6215c0a5a..1c93e5ec5 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "test": "npm run test:app", "test:all": "npm run test:typecheck && npm run test:code && npm run test:other && npm run test:app -- --watchAll=false", "test:update": "npm run test:app -- --updateSnapshot --watchAll=false", - "test:app": "react-scripts test --env=jsdom --passWithNoTests", + "test:app": "react-scripts test --env=jsdom-fourteen --passWithNoTests", "test:code": "eslint --max-warnings=0 --ignore-path .gitignore --ext .js,.ts,.tsx .", "test:debug": "react-scripts --inspect-brk test --runInBand --no-cache", "test:other": "npm run prettier -- --list-different", diff --git a/src/components/InitializeApp.tsx b/src/components/InitializeApp.tsx new file mode 100644 index 000000000..091f726d7 --- /dev/null +++ b/src/components/InitializeApp.tsx @@ -0,0 +1,24 @@ +import React from "react"; + +import { LoadingMessage } from "./LoadingMessage"; +import { setLanguageFirstTime } from "../i18n"; + +export class InitializeApp extends React.Component< + any, + { isLoading: boolean } +> { + public state: { isLoading: boolean } = { + isLoading: true, + }; + + async componentDidMount() { + await setLanguageFirstTime(); + this.setState({ + isLoading: false, + }); + } + + public render() { + return this.state.isLoading ? : this.props.children; + } +} diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx index 57e0fc348..9f52b919c 100644 --- a/src/components/LayerUI.tsx +++ b/src/components/LayerUI.tsx @@ -255,8 +255,8 @@ const LayerUI = ({ }`} > { - setLanguage(lng); + onChange={async (lng) => { + await setLanguage(lng); setAppState({}); }} languages={languages} diff --git a/src/components/MobileMenu.tsx b/src/components/MobileMenu.tsx index dbd7f672d..d2f5347a4 100644 --- a/src/components/MobileMenu.tsx +++ b/src/components/MobileMenu.tsx @@ -100,8 +100,8 @@ export const MobileMenu = ({
{t("labels.language")} { - setLanguage(lng); + onChange={async (lng) => { + await setLanguage(lng); setAppState({}); }} /> diff --git a/src/i18n.ts b/src/i18n.ts index 9d39d165b..57e3c42dc 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -1,55 +1,58 @@ import LanguageDetector from "i18next-browser-languagedetector"; export const languages = [ - { lng: "en", label: "English", data: require("./locales/en.json") }, - { lng: "bg-BG", label: "Български", data: require("./locales/bg-BG.json") }, - { lng: "nb-No", label: "Bokmål", data: require("./locales/nb-NO.json") }, - { lng: "de-DE", label: "Deutsch", data: require("./locales/de-DE.json") }, - { lng: "es-ES", label: "Español", data: require("./locales/es-ES.json") }, - { lng: "ca-ES", label: "Catalan", data: require("./locales/ca-ES.json") }, - { lng: "el-GR", label: "Ελληνικά", data: require("./locales/el-GR.json") }, - { lng: "fr-FR", label: "Français", data: require("./locales/fr-FR.json") }, - { - lng: "id-ID", - label: "Bahasa Indonesia", - data: require("./locales/id-ID.json"), - }, - { lng: "it-IT", label: "Italiano", data: require("./locales/it-IT.json") }, - { lng: "hu-HU", label: "Magyar", data: require("./locales/hu-HU.json") }, - { lng: "nl-NL", label: "Nederlands", data: require("./locales/nl-NL.json") }, - { lng: "pl-PL", label: "Polski", data: require("./locales/pl-PL.json") }, - { lng: "pt-PT", label: "Português", data: require("./locales/pt-PT.json") }, - { lng: "ru-RU", label: "Русский", data: require("./locales/ru-RU.json") }, - { lng: "uk-UA", label: "Українська", data: require("./locales/uk-UA.json") }, - { lng: "fi-FI", label: "Suomi", data: require("./locales/fi-FI.json") }, - { lng: "tr-TR", label: "Türkçe", data: require("./locales/tr-TR.json") }, - { lng: "ja-JP", label: "日本語", data: require("./locales/ja-JP.json") }, - { lng: "ko-KR", label: "한국어", data: require("./locales/ko-KR.json") }, - { lng: "zh-TW", label: "繁體中文", data: require("./locales/zh-TW.json") }, - { lng: "zh-CN", label: "简体中文", data: require("./locales/zh-CN.json") }, - { - lng: "ar-SA", - label: "العربية", - data: require("./locales/ar-SA.json"), - rtl: true, - }, - { - lng: "he-IL", - label: "עברית", - data: require("./locales/he-IL.json"), - rtl: true, - }, + { lng: "en", label: "English", data: "en.json" }, + { lng: "bg-BG", label: "Български", data: "bg-BG.json" }, + { lng: "de-DE", label: "Deutsch", data: "de-DE.json" }, + { lng: "nb-No", label: "Bokmål", data: "nb-NO.json" }, + { lng: "es-ES", label: "Español", data: "es-ES.json" }, + { lng: "ca-ES", label: "Catalan", data: "ca-ES.json" }, + { lng: "el-GR", label: "Ελληνικά", data: "el-GR.json" }, + { lng: "fr-FR", label: "Français", data: "fr-FR.json" }, + { lng: "id-ID", label: "Bahasa Indonesia", data: "id-ID.json" }, + { lng: "it-IT", label: "Italiano", data: "it-IT.json" }, + { lng: "hu-HU", label: "Magyar", data: "hu-HU.json" }, + { lng: "nl-NL", label: "Nederlands", data: "nl-NL.json" }, + { lng: "pl-PL", label: "Polski", data: "pl-PL.json" }, + { lng: "pt-PT", label: "Português", data: "pt-PT.json" }, + { lng: "ru-RU", label: "Русский", data: "ru-RU.json" }, + { lng: "uk-UA", label: "Українська", data: "uk-UA.json" }, + { lng: "fi-FI", label: "Suomi", data: "fi-FI.json" }, + { lng: "tr-TR", label: "Türkçe", data: "tr-TR.json" }, + { lng: "ja-JP", label: "日本語", data: "ja-JP.json" }, + { lng: "ko-KR", label: "한국어", data: "ko-KR.json" }, + { lng: "zh-TW", label: "繁體中文", data: "zh-TW.json" }, + { lng: "zh-CN", label: "简体中文", data: "zh-CN.json" }, + { lng: "ar-SA", label: "العربية", data: "ar-SA.json", rtl: true }, + { lng: "he-IL", label: "עברית", data: "he-IL.json", rtl: true }, ]; let currentLanguage = languages[0]; +let currentLanguageData = {}; const fallbackLanguage = languages[0]; +const fallbackLanguageData = require("./locales/en.json"); -export const setLanguage = (newLng: string | undefined) => { +export const setLanguage = async (newLng: string | undefined) => { currentLanguage = languages.find((language) => language.lng === newLng) || fallbackLanguage; document.documentElement.dir = currentLanguage.rtl ? "rtl" : "ltr"; + currentLanguageData = await import(`./locales/${currentLanguage.data}`); + + languageDetector.cacheUserLanguage(currentLanguage.lng); +}; + +export const setLanguageFirstTime = async () => { + const newLng: string | undefined = languageDetector.detect(); + + currentLanguage = + languages.find((language) => language.lng === newLng) || fallbackLanguage; + + document.documentElement.dir = currentLanguage.rtl ? "rtl" : "ltr"; + + currentLanguageData = await import(`./locales/${currentLanguage.data}`); + languageDetector.cacheUserLanguage(currentLanguage.lng); }; @@ -72,8 +75,8 @@ const findPartsForData = (data: any, parts: string[]) => { export const t = (path: string, replacement?: { [key: string]: string }) => { const parts = path.split("."); let translation = - findPartsForData(currentLanguage.data, parts) || - findPartsForData(fallbackLanguage.data, parts); + findPartsForData(currentLanguageData, parts) || + findPartsForData(fallbackLanguageData, parts); if (translation === undefined) { throw new Error(`Can't find translation for ${path}`); } @@ -94,5 +97,3 @@ languageDetector.init({ }, checkWhitelist: false, }); - -setLanguage(languageDetector.detect()); diff --git a/src/index.tsx b/src/index.tsx index 38a7b6fb3..f78ba77cb 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -5,6 +5,7 @@ import * as SentryIntegrations from "@sentry/integrations"; import { EVENT } from "./constants"; import { TopErrorBoundary } from "./components/TopErrorBoundary"; +import { InitializeApp } from "./components/InitializeApp"; import { IsMobileProvider } from "./is-mobile"; import App from "./components/App"; import { register as registerServiceWorker } from "./serviceWorker"; @@ -67,7 +68,9 @@ const rootElement = document.getElementById("root"); ReactDOM.render( - + + + , rootElement, diff --git a/src/tests/regressionTests.test.tsx b/src/tests/regressionTests.test.tsx index 3209000d4..9c07532df 100644 --- a/src/tests/regressionTests.test.tsx +++ b/src/tests/regressionTests.test.tsx @@ -2,8 +2,9 @@ import { reseed } from "../random"; import React from "react"; import ReactDOM from "react-dom"; import * as Renderer from "../renderer/renderScene"; -import { render, screen, fireEvent } from "./test-utils"; +import { waitFor, render, screen, fireEvent } from "./test-utils"; import App from "../components/App"; +import { setLanguage } from "../i18n"; import { ToolName } from "./queries/toolQueries"; import { KEYS, Key } from "../keys"; import { setDateTimeForTests } from "../utils"; @@ -227,7 +228,7 @@ const checkpoint = (name: string) => { ); }; -beforeEach(() => { +beforeEach(async () => { // Unmount ReactDOM from root ReactDOM.unmountComponentAtNode(document.getElementById("root")!); @@ -242,6 +243,7 @@ beforeEach(() => { finger2.reset(); altKey = ctrlKey = shiftKey = false; + await setLanguage("en.json"); const renderResult = render(); getByToolName = renderResult.getByToolName; @@ -655,7 +657,7 @@ describe("regression tests", () => { expect(h.state.zoom).toBe(1); }); - it("rerenders UI on language change", () => { + it("rerenders UI on language change", async () => { // select rectangle tool to show properties menu clickTool("rectangle"); // english lang should display `hachure` label @@ -664,11 +666,13 @@ describe("regression tests", () => { target: { value: "de-DE" }, }); // switching to german, `hachure` label should no longer exist - expect(screen.queryByText(/hachure/i)).toBeNull(); + await waitFor(() => expect(screen.queryByText(/hachure/i)).toBeNull()); // reset language fireEvent.change(document.querySelector(".dropdown-select__language")!, { target: { value: "en" }, }); + // switching back to English + await waitFor(() => expect(screen.queryByText(/hachure/i)).not.toBeNull()); }); it("make a group and duplicate it", () => {