Skip to content
This repository has been archived by the owner on Apr 25, 2023. It is now read-only.

Commit

Permalink
chore: introduce i18next (#212)
Browse files Browse the repository at this point in the history
  • Loading branch information
rot1024 committed Apr 27, 2022
1 parent 9a59938 commit 0ac0c2d
Show file tree
Hide file tree
Showing 26 changed files with 696 additions and 61 deletions.
6 changes: 2 additions & 4 deletions .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@
],
"plugins": [
"@emotion",
"@babel/proposal-optional-chaining",
"@babel/proposal-nullish-coalescing-operator",
"@babel/plugin-transform-typescript",
"react-intl-auto"
"react-intl-auto" // delete when migration to i18next is complete
],
"env": {
"test": {
Expand All @@ -32,4 +30,4 @@
]
}
}
}
}
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
steps:
- uses: actions/setup-node@v2
with:
node-version: '14.x'
node-version: "14.x"
- name: Checkout
uses: actions/checkout@v2
- name: Get yarn cache directory path
Expand All @@ -33,6 +33,8 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage/lcov.info
- name: Check translations
run: yarn run i18n --fail-on-update
slack-notification:
if: always()
name: Slack Notification
Expand Down
6 changes: 3 additions & 3 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from "@apollo/client";
import React, { ReactElement } from "react";

import { Provider as IntlProvider } from "../src/locale";
import { Provider as I18nProvider } from "../src/i18n";
import { Provider as ThemeProvider } from "../src/theme";
import { Provider as DndProvider } from "../src/util/use-dnd";

Expand All @@ -31,9 +31,9 @@ export const decorators = [
(storyFn: () => ReactElement) => (
<ApolloProvider client={mockClient}>
<ThemeProvider>
<IntlProvider>
<I18nProvider>
<DndProvider>{storyFn()}</DndProvider>
</IntlProvider>
</I18nProvider>
</ThemeProvider>
</ApolloProvider>
),
Expand Down
6 changes: 6 additions & 0 deletions i18next-parser.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
locales: ["en", "ja"],
output: "src/i18n/translations/$LOCALE.yml",
input: ["src/**/*.{ts,tsx}"],
useKeysAsDefaultValue: true,
};
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
"storybook:screenshot": "storycap --serverCmd 'npx http-server ./storybook-static -p 6006' --serverTimeout 100000 http://localhost:6006 --max-old-space-size=2048",
"cypress:open": "cypress open",
"cypress:run": "cypress run",
"gen": "run-p gen:*",
"gen:gql": "graphql-codegen --config codegen.yml",
"gen:i18n": "extract-messages -d en -l=en,ja -o translations -f yaml 'src/**/*.ts*' --extractFromFormatMessageCall",
"gql": "graphql-codegen --config codegen.yml",
"i18n": "i18next",
"i18n:legacy": "extract-messages -d en -l=en,ja -o src/i18n/translations/legacy -f yaml 'src/**/*.ts*' --extractFromFormatMessageCall",
"gen:doc:plugin": "ts-node -O '{\"module\":\"CommonJS\"}' ./bin/pluginDoc"
},
"engines": {
Expand Down Expand Up @@ -127,6 +127,7 @@
"html-webpack-plugin": "^5.4.0",
"html-webpack-tags-plugin": "^3.0.0",
"husky": "^7.0.4",
"i18next-parser": "^6.3.0",
"jest": "^27.5.0",
"jest-transform-yaml": "^1.0.0",
"json-loader": "^0.5.7",
Expand Down Expand Up @@ -177,6 +178,8 @@
"github-markdown-css": "^5.1.0",
"graphiql": "^1.8.7",
"graphql": "^16.3.0",
"i18next": "^21.6.16",
"i18next-browser-languagedetector": "^6.1.4",
"jotai": "^1.6.4",
"js-file-download": "^0.4.12",
"leaflet": "^1.7.1",
Expand All @@ -195,6 +198,7 @@
"react-dom": "^17.0.2",
"react-dropzone": "^12.0.5",
"react-ga": "^3.3.0",
"react-i18next": "^11.16.7",
"react-intl": "^5.24.6",
"react-leaflet": "^3.2.5",
"react-markdown": "^8.0.2",
Expand Down
6 changes: 3 additions & 3 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import SettingsProjectList from "@reearth/components/pages/Settings/ProjectList"
import WorkspaceSettings from "@reearth/components/pages/Settings/Workspace";
import AssetSettings from "@reearth/components/pages/Settings/Workspace/Asset";
import WorkspaceList from "@reearth/components/pages/Settings/WorkspaceList";
import { Provider as IntlProvider } from "@reearth/locale";
import { Provider as I18nProvider } from "@reearth/i18n";

import { Provider as Auth0Provider } from "./auth";
import RootPage from "./components/pages/Authentication/RootPage";
Expand Down Expand Up @@ -44,7 +44,7 @@ const App: React.FC = () => {
<Auth0Provider>
<GqlProvider>
<ThemeProvider>
<IntlProvider>
<I18nProvider>
<Suspense fallback={<Loading />}>
<NotificationBanner />
<StyledRouter>
Expand All @@ -70,7 +70,7 @@ const App: React.FC = () => {
<NotFound default />
</StyledRouter>
</Suspense>
</IntlProvider>
</I18nProvider>
</ThemeProvider>
</GqlProvider>
</Auth0Provider>
Expand Down
19 changes: 5 additions & 14 deletions src/components/atoms/ConfirmationModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import { useIntl } from "react-intl";

import { useTranslation } from "@reearth/i18n";

import Button from "../Button";
import Modal from "../Modal";
Expand All @@ -23,7 +24,7 @@ const ConfirmationModal: React.FC<Props> = ({
isOpen,
onClose,
}) => {
const intl = useIntl();
const { t } = useTranslation();

const handleProceed = () => {
onProceed();
Expand All @@ -37,19 +38,9 @@ const ConfirmationModal: React.FC<Props> = ({
size="sm"
onClose={onClose}
button1={
<Button
text={buttonAction || intl.formatMessage({ defaultMessage: "Continue" })}
onClick={handleProceed}
buttonType="danger"
/>
<Button text={buttonAction || t("Continue")} onClick={handleProceed} buttonType="danger" />
}
button2={
<Button
text={intl.formatMessage({ defaultMessage: "Cancel" })}
onClick={onCancel ?? onClose}
buttonType="secondary"
/>
}>
button2={<Button text={t("Cancel")} onClick={onCancel ?? onClose} buttonType="secondary" />}>
{body}
</Modal>
);
Expand Down
6 changes: 3 additions & 3 deletions src/components/molecules/Dashboard/Workspace.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Link } from "@reach/router";
import React, { useMemo } from "react";
import { useIntl } from "react-intl";
import { useMedia } from "react-use";

import Avatar from "@reearth/components/atoms/Avatar";
Expand All @@ -9,6 +8,7 @@ import Flex from "@reearth/components/atoms/Flex";
import Icon from "@reearth/components/atoms/Icon";
import Text from "@reearth/components/atoms/Text";
import { Team as TeamType } from "@reearth/components/molecules/Dashboard/types";
import { useTranslation } from "@reearth/i18n";
import { styled, useTheme, metrics } from "@reearth/theme";
import { metricsSizes } from "@reearth/theme/metrics";

Expand All @@ -20,7 +20,7 @@ export interface Props {
}

const Workspace: React.FC<Props> = ({ className, team }) => {
const intl = useIntl();
const { t } = useTranslation();
const theme = useTheme();
const isSmallWindow = useMedia("(max-width: 1024px)");

Expand All @@ -37,7 +37,7 @@ const Workspace: React.FC<Props> = ({ className, team }) => {
<Content direction="column" justify="space-between">
<Text size={isSmallWindow ? "m" : "l"} color={theme.main.text} weight="bold">
{team?.name}
{intl.formatMessage({ defaultMessage: "'s workspace" })}
{t("'s workspace")}
</Text>
<Flex>
<Flex flex={4}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import PasswordModal, {
import Field from "@reearth/components/molecules/Settings/Field";
import EditableItem from "@reearth/components/molecules/Settings/Project/EditableItem";
import Section from "@reearth/components/molecules/Settings/Section";
import { localesWithLabel } from "@reearth/locale";
import { localesWithLabel } from "@reearth/i18n/legacy";
import { styled } from "@reearth/theme";

export type Theme = "dark" | "light" | "default";
Expand Down
2 changes: 1 addition & 1 deletion src/components/pages/Authentication/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useCallback, useEffect } from "react";

import { useAuth, useCleanUrl } from "@reearth/auth";
import { useTeamsQuery } from "@reearth/gql";
import { intl } from "@reearth/locale";
import { intl } from "@reearth/i18n/legacy";
import { useTeam, useNotification } from "@reearth/state";

// TODO: move hooks to molecules (page components should be thin)
Expand Down
27 changes: 27 additions & 0 deletions src/i18n/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import i18n from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import { initReactI18next } from "react-i18next";

import en from "./translations/en.yml";
import ja from "./translations/ja.yml";

const resources = {
en: {
translation: en,
},
ja: {
translation: ja,
},
};

export const availableLanguages = Object.keys(resources);

// eslint-disable-next-line import/no-named-as-default-member
i18n.use(LanguageDetector).use(initReactI18next).init({
resources,
fallbackLng: "en",
keySeparator: false,
returnEmptyString: false,
});

export default i18n;
3 changes: 3 additions & 0 deletions src/i18n/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { useTranslation } from "react-i18next";
export { default as Provider } from "./provider";
export { default as PublishedProvider } from "./publishedProvider";
File renamed without changes.
4 changes: 2 additions & 2 deletions src/locale/locale.ts → src/i18n/legacy/locale.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createIntl, createIntlCache } from "react-intl";

import en from "../../translations/en.yml";
import ja from "../../translations/ja.yml";
import en from "../translations/legacy/en.yml";
import ja from "../translations/legacy/ja.yml";

export const locales = ["en", "ja"] as const;

Expand Down
4 changes: 2 additions & 2 deletions src/locale/provider.tsx → src/i18n/legacy/provider.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { PropsWithChildren } from "react";
import React, { ReactNode } from "react";
import { IntlProvider } from "react-intl";

import { useAuth } from "@reearth/auth";
import { useLanguageQuery } from "@reearth/gql";

import { Locale, defaultLocale, locales, messages } from "./locale";

export default function Provider({ children }: PropsWithChildren<{}>) {
export default function Provider({ children }: { children?: ReactNode }) {
const { isAuthenticated } = useAuth();
const { data } = useLanguageQuery({ skip: !isAuthenticated });
const locale =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { PropsWithChildren } from "react";
import React, { ReactNode } from "react";
import { IntlProvider } from "react-intl";

import { defaultLocale, messages } from "./locale";

const locale = navigator.language.split("_")[0];

export default function PublishedProvider({ children }: PropsWithChildren<{}>) {
export default function PublishedProvider({ children }: { children?: ReactNode }) {
return (
<IntlProvider locale={locale} defaultLocale={defaultLocale} messages={messages[locale]}>
{children}
Expand Down
24 changes: 24 additions & 0 deletions src/i18n/provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { ReactNode, useEffect } from "react";
import { I18nextProvider } from "react-i18next";

import { useAuth } from "@reearth/auth";
import { useLanguageQuery } from "@reearth/gql";

import i18n from "./i18n";
import { Provider as LegacyProvider } from "./legacy";

export default function Provider({ children }: { children?: ReactNode }) {
const { isAuthenticated } = useAuth();
const { data } = useLanguageQuery({ skip: !isAuthenticated });
const locale = data?.me?.lang;

useEffect(() => {
i18n.changeLanguage(locale === "und" ? undefined : locale);
}, [locale]);

return (
<I18nextProvider i18n={i18n}>
<LegacyProvider>{children}</LegacyProvider>
</I18nextProvider>
);
}
13 changes: 13 additions & 0 deletions src/i18n/publishedProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React, { ReactNode } from "react";
import { I18nextProvider } from "react-i18next";

import i18n from "./i18n";
import { PublishedProvider as LegacyProvider } from "./legacy";

export default function PublishedProvider({ children }: { children?: ReactNode }) {
return (
<I18nextProvider i18n={i18n}>
<LegacyProvider>{children}</LegacyProvider>
</I18nextProvider>
);
}
3 changes: 3 additions & 0 deletions src/i18n/translations/en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Continue: Continue
Cancel: Cancel
'''s workspace': '''s workspace'
3 changes: 3 additions & 0 deletions src/i18n/translations/ja.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Continue: キャンセル
Cancel: 続ける
'''s workspace': ' のワークスペース'
File renamed without changes.
File renamed without changes.
6 changes: 3 additions & 3 deletions src/publishedapp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import React from "react";

import PublishedPage from "@reearth/components/pages/Published";

import { PublishedProvider as IntlProvider } from "./locale";
import { Provider as I18nProvider } from "./i18n";
import { PublishedAppProvider as ThemeProvider } from "./theme";
import { Provider as DndProvider } from "./util/use-dnd";

export default function App() {
return (
<ThemeProvider>
<DndProvider>
<IntlProvider>
<I18nProvider>
<PublishedPage />
</IntlProvider>
</I18nProvider>
</DndProvider>
</ThemeProvider>
);
Expand Down
4 changes: 2 additions & 2 deletions src/test/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { MockedProvider as MockedGqlProvider, MockedResponse } from "@apollo/cli
import { render as rtlRender } from "@testing-library/react";
import React from "react";

import { Provider as IntlProvider } from "../locale";
import { Provider as I18nProvider } from "../i18n";
import { Provider as ThemeProvider } from "../theme";

const render = (
Expand All @@ -14,7 +14,7 @@ const render = (
return (
<MockedGqlProvider mocks={queryMocks} addTypename={false}>
<ThemeProvider>
<IntlProvider>{children}</IntlProvider>
<I18nProvider>{children}</I18nProvider>
</ThemeProvider>
</MockedGqlProvider>
);
Expand Down
1 change: 1 addition & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ module.exports = (env, args = {}) => {
},
{
test: /\.ya?ml$/,
// Delete yaml-flat-loader and use a regular yaml loader when migration to i18next is complete
use: [{ loader: "json-loader" }, { loader: "yaml-flat-loader" }],
},
{
Expand Down
Loading

0 comments on commit 0ac0c2d

Please sign in to comment.