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

chore: introduce i18next #212

Merged
merged 14 commits into from
Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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': ' のワークスペース'
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