From f9af0501122614625042fdf0fb1be7a67a6ff03a Mon Sep 17 00:00:00 2001 From: mashharuki Date: Wed, 1 Jan 2025 20:00:54 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E3=81=93=E3=81=93=E3=81=BE=E3=81=A7?= =?UTF-8?q?=E3=81=AE=E6=9B=B4=E6=96=B0=E5=88=86=E3=82=92=E3=83=97=E3=83=83?= =?UTF-8?q?=E3=82=B7=E3=83=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkgs/frontend/app/config/i18n.ts | 12 ++ pkgs/frontend/app/config/i18next.server.ts | 38 ++++++ pkgs/frontend/app/entry.client.tsx | 35 +++++- pkgs/frontend/app/entry.server.tsx | 90 ++++++++++++--- pkgs/frontend/app/root.tsx | 29 ++++- pkgs/frontend/app/routes/_index.tsx | 8 +- pkgs/frontend/package.json | 8 +- pkgs/frontend/public/favicon.ico | Bin 16958 -> 16958 bytes pkgs/frontend/public/locales/en/common.json | 3 + pkgs/frontend/public/locales/ja/common.json | 3 + pkgs/frontend/remix.config.js | 7 ++ pkgs/frontend/vite.config.ts | 5 + yarn.lock | 122 ++++++++++++++------ 13 files changed, 297 insertions(+), 63 deletions(-) create mode 100644 pkgs/frontend/app/config/i18n.ts create mode 100644 pkgs/frontend/app/config/i18next.server.ts create mode 100644 pkgs/frontend/public/locales/en/common.json create mode 100644 pkgs/frontend/public/locales/ja/common.json create mode 100644 pkgs/frontend/remix.config.js diff --git a/pkgs/frontend/app/config/i18n.ts b/pkgs/frontend/app/config/i18n.ts new file mode 100644 index 0000000..0975fe5 --- /dev/null +++ b/pkgs/frontend/app/config/i18n.ts @@ -0,0 +1,12 @@ +import type { InitOptions } from "i18next"; + +export const i18nConfig = { + // Supported languages. + supportedLngs: ["en", "ja"], + // Fallback language. + fallbackLng: "en", + // Default namespace. + defaultNS: "common", + // Disable suspense mode. (Recommended). + react: { useSuspense: false }, +} satisfies InitOptions; diff --git a/pkgs/frontend/app/config/i18next.server.ts b/pkgs/frontend/app/config/i18next.server.ts new file mode 100644 index 0000000..903be4c --- /dev/null +++ b/pkgs/frontend/app/config/i18next.server.ts @@ -0,0 +1,38 @@ +import { createCookie } from "@remix-run/node"; +import { resolve } from "node:path"; + +import { RemixI18Next } from "remix-i18next"; +import { i18nConfig } from "~/config/i18n.js"; + +import Backend from "i18next-fs-backend"; + +export const i18nCookie = createCookie("_i18n", { + path: "/", + sameSite: "lax", + httpOnly: true, + maxAge: 60, // 1 minute - We do not require a long session for i18n. +}); + +export const remixI18Next = new RemixI18Next({ + detection: { + // The cookie used to store user's language preference. + cookie: i18nCookie, + // The supported languages. + supportedLanguages: i18nConfig.supportedLngs, + // The fallback language. + fallbackLanguage: i18nConfig.fallbackLng, + }, + + // The i18next configuration used when translating + // Server-Side only messages. + i18next: { + ...i18nConfig, + backend: { + loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json"), + }, + }, + + // The plugins you want RemixI18next to use for `i18n.getFixedT` inside loaders and actions. + // E.g. The Backend plugin for loading translations from the file system. + plugins: [Backend], +}); diff --git a/pkgs/frontend/app/entry.client.tsx b/pkgs/frontend/app/entry.client.tsx index 1a0662d..b479124 100644 --- a/pkgs/frontend/app/entry.client.tsx +++ b/pkgs/frontend/app/entry.client.tsx @@ -4,14 +4,45 @@ import { hydrateRoot } from "react-dom/client"; import { ChakraProvider } from "./components/chakra-provider"; import { ClientCacheProvider } from "./emotion/emotion-client"; -const hydrate = () => { +import { I18nextProvider, initReactI18next } from "react-i18next"; +import { getInitialNamespaces } from "remix-i18next"; +import { i18nConfig } from "~/config/i18n.js"; + +import i18next from "i18next"; +import LanguageDetector from "i18next-browser-languagedetector"; +import Backend from "i18next-http-backend"; + +const hydrate = async () => { + await i18next + // Use the react-i18next plugin. + .use(initReactI18next) + // Setup client-side language detector. + .use(LanguageDetector) + // Setup backend. + .use(Backend) + .init({ + // Spread configuration. + ...i18nConfig, + // Detects the namespaces your routes rendered while SSR use + // and pass them here to load the translations. + ns: getInitialNamespaces(), + backend: { loadPath: "/locales/{{lng}}/{{ns}}.json" }, + detection: { + // We'll detect the language only server-side with remix-i18next. + // By using `` attribute we communicate to the Client. + order: ["htmlTag"], + }, + }); + startTransition(() => { hydrateRoot( document, - + + + diff --git a/pkgs/frontend/app/entry.server.tsx b/pkgs/frontend/app/entry.server.tsx index e7f534f..c461e0e 100644 --- a/pkgs/frontend/app/entry.server.tsx +++ b/pkgs/frontend/app/entry.server.tsx @@ -1,28 +1,90 @@ import type { EntryContext } from "@remix-run/node"; import { RemixServer } from "@remix-run/react"; -import { createEmotion } from "./emotion/emotion-server"; -const handleRequest = ( +import { createReadableStreamFromReadable } from "@remix-run/node"; +import { isbot } from "isbot"; +import { resolve } from "node:path"; +import { PassThrough } from "node:stream"; +import { renderToPipeableStream } from "react-dom/server"; + +import { createInstance } from "i18next"; +import Backend from "i18next-fs-backend"; +import { I18nextProvider, initReactI18next } from "react-i18next"; +import { i18nConfig } from "~/config/i18n.js"; +import { remixI18Next } from "~/config/i18next.server.js"; + +const ABORT_DELAY = 5_000; + +const handleRequest = async ( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext -) => - new Promise((resolve) => { - const { renderToString, injectStyles } = createEmotion(); +) => { + const callbackName = isbot(request.headers.get("user-agent")) + ? "onAllReady" + : "onShellReady"; - const html = renderToString( - - ); - - responseHeaders.set("Content-Type", "text/html"); + // Internationalization (i18n). + const i18nInstance = createInstance(); + const lng = await remixI18Next.getLocale(request); + const ns = remixI18Next.getRouteNamespaces(remixContext); - const response = new Response(`${injectStyles(html)}`, { - status: responseStatusCode, - headers: responseHeaders, + await i18nInstance + .use(initReactI18next) // Tell our instance to use react-i18next. + .use(Backend) // Setup backend. + .init({ + ...i18nConfig, // Spread configuration. + lng, // Locale detected above. + ns, // Namespaces detected above. + backend: { loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json") }, }); - resolve(response); + new Promise((resolve, reject) => { + let shellRendered = false; + + const { pipe, abort } = renderToPipeableStream( + + + , + { + [callbackName]: () => { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set("Content-Type", "text/html"); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }) + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. + // Don't log errors encountered during initial shell rendering, + // since they'll reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + } + ); + + setTimeout(abort, ABORT_DELAY); }); +}; export default handleRequest; diff --git a/pkgs/frontend/app/root.tsx b/pkgs/frontend/app/root.tsx index 46253f9..019d9fb 100644 --- a/pkgs/frontend/app/root.tsx +++ b/pkgs/frontend/app/root.tsx @@ -7,16 +7,35 @@ import { ScrollRestoration, } from "@remix-run/react"; // import { ThemeProvider } from "next-themes"; // DarkMode 切り替えの実装の可能性に備え、ThemeProvider を残しておいてあります -import { ChakraProvider } from "./components/chakra-provider"; -import { useInjectStyles } from "./emotion/emotion-client"; -import { PrivyProvider } from "@privy-io/react-auth"; -import { Box, Container } from "@chakra-ui/react"; -import { Header } from "./components/Header"; import { ApolloProvider } from "@apollo/client/react"; +import { Box, Container } from "@chakra-ui/react"; +import { PrivyProvider } from "@privy-io/react-auth"; +import type { LoaderFunctionArgs } from "@remix-run/node"; import { goldskyClient } from "utils/apollo"; +import { i18nCookie, remixI18Next } from "~/config/i18next.server"; +import { ChakraProvider } from "./components/chakra-provider"; +import { Header } from "./components/Header"; +import { useInjectStyles } from "./emotion/emotion-client"; interface LayoutProps extends React.PropsWithChildren {} +export const handle = { + // In the handle export, we can specify i18n namespaces needed for the route. + // Usually, we'll set it to our default namespace or "translation" if haven't set one. + // It can be a string or an array. + i18n: "common", +}; + +export async function loader({ request }: LoaderFunctionArgs) { + const locale = await remixI18Next.getLocale(request); + + return Response.json({ locale } as const, { + headers: { + "set-cookie": await i18nCookie.serialize(locale), + }, + }); +} + export const Layout = withEmotionCache((props: LayoutProps, cache) => { const { children } = props; diff --git a/pkgs/frontend/app/routes/_index.tsx b/pkgs/frontend/app/routes/_index.tsx index dabd1c3..c94136f 100644 --- a/pkgs/frontend/app/routes/_index.tsx +++ b/pkgs/frontend/app/routes/_index.tsx @@ -1,16 +1,16 @@ import { Box, Input } from "@chakra-ui/react"; import type { MetaFunction } from "@remix-run/node"; -import { CommonButton } from "~/components/common/CommonButton"; import { useBigBang } from "hooks/useBigBang"; import { - useUploadMetadataToIpfs, useUploadImageFileToIpfs, + useUploadMetadataToIpfs, } from "hooks/useIpfs"; +import { CommonButton } from "~/components/common/CommonButton"; export const meta: MetaFunction = () => { return [ - { title: "New Remix App" }, - { name: "description", content: "Welcome to Remix!" }, + { title: "Toban" }, + { name: "description", content: "Welcome to Toban!" }, ]; }; diff --git a/pkgs/frontend/package.json b/pkgs/frontend/package.json index b2b9f42..3e28d9e 100644 --- a/pkgs/frontend/package.json +++ b/pkgs/frontend/package.json @@ -39,7 +39,13 @@ "react-dom": "^18.3.1", "react-hook-form": "^7.54.2", "react-icons": "^5.4.0", - "viem": "^2.21.51" + "viem": "^2.21.51", + "i18next": "^23.5.1", + "i18next-browser-languagedetector": "^7.1.0", + "i18next-fs-backend": "^2.2.0", + "i18next-http-backend": "^2.2.2", + "react-i18next": "^13.2.2", + "remix-i18next": "^5.4.0" }, "devDependencies": { "@graphql-codegen/cli": "5.0.3", diff --git a/pkgs/frontend/public/favicon.ico b/pkgs/frontend/public/favicon.ico index 8830cf6821b354114848e6354889b8ecf6d2bc61..8e26e7cdf74014dcb3b5ec392e1267f0acaed3d1 100644 GIT binary patch literal 16958 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 literal 16958 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 EKbET!0{{R3 diff --git a/pkgs/frontend/public/locales/en/common.json b/pkgs/frontend/public/locales/en/common.json new file mode 100644 index 0000000..ad06d0b --- /dev/null +++ b/pkgs/frontend/public/locales/en/common.json @@ -0,0 +1,3 @@ +{ + "title": "remix-i18n is awesome" +} diff --git a/pkgs/frontend/public/locales/ja/common.json b/pkgs/frontend/public/locales/ja/common.json new file mode 100644 index 0000000..a0d4cb6 --- /dev/null +++ b/pkgs/frontend/public/locales/ja/common.json @@ -0,0 +1,3 @@ +{ + "title": "remix-i18n は凄い!" +} diff --git a/pkgs/frontend/remix.config.js b/pkgs/frontend/remix.config.js new file mode 100644 index 0000000..bd41d58 --- /dev/null +++ b/pkgs/frontend/remix.config.js @@ -0,0 +1,7 @@ +/** + * @type {import('@remix-run/dev').AppConfig} + */ +export default { + ignoredRouteFiles: ["**/.*"], + //serverDependenciesToBundle: ["remix-i18next"], +}; diff --git a/pkgs/frontend/vite.config.ts b/pkgs/frontend/vite.config.ts index b632cba..152fb04 100644 --- a/pkgs/frontend/vite.config.ts +++ b/pkgs/frontend/vite.config.ts @@ -14,4 +14,9 @@ export default defineConfig({ }), tsconfigPaths(), ], + server: { + hmr: { + overlay: false, + }, + }, }); diff --git a/yarn.lock b/yarn.lock index 6e3a087..132834b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1398,7 +1398,7 @@ core-js-pure "^3.30.2" regenerator-runtime "^0.14.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.25.0", "@babel/runtime@^7.25.9", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.25.0", "@babel/runtime@^7.25.9", "@babel/runtime@^7.8.4": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== @@ -7323,6 +7323,11 @@ abortcontroller-polyfill@^1.7.5: resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.6.tgz#7be8d35b5ed7dfa1a51b36f221720b23deb13f36" integrity sha512-Zypm+LjYdWAzvuypZvDN0smUJrhOurcuBWhhMRBExqVLRvdjp3Z9mASxKyq19K+meZMshwjjy5S0lkm388zE4Q== +accept-language-parser@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/accept-language-parser/-/accept-language-parser-1.5.0.tgz#8877c54040a8dcb59e0a07d9c1fde42298334791" + integrity sha512-QhyTbMLYo0BBGg1aWbeMG4ekWtds/31BrEU+DONOg/7ax23vxpL03Pb7/zBmha2v7vdD3AyzZVWBVGEZxKOXWw== + accepts@~1.3.4, accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -9080,6 +9085,13 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cross-fetch@4.0.0, cross-fetch@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" + integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== + dependencies: + node-fetch "^2.6.12" + cross-fetch@^3.1.4, cross-fetch@^3.1.5: version "3.1.8" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" @@ -9087,13 +9099,6 @@ cross-fetch@^3.1.4, cross-fetch@^3.1.5: dependencies: node-fetch "^2.6.12" -cross-fetch@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" - integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== - dependencies: - node-fetch "^2.6.12" - cross-inspect@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cross-inspect/-/cross-inspect-1.0.1.tgz#15f6f65e4ca963cf4cc1a2b5fef18f6ca328712b" @@ -12217,6 +12222,13 @@ html-minifier-terser@^7.2.0: relateurl "^0.2.7" terser "^5.15.1" +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== + dependencies: + void-elements "3.1.0" + html-tags@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" @@ -12401,6 +12413,32 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" +i18next-browser-languagedetector@^7.1.0: + version "7.2.2" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.2.tgz#748e7dc192847613911d8a79d9d9a6c2d266133e" + integrity sha512-6b7r75uIJDWCcCflmbof+sJ94k9UQO4X0YR62oUfqGI/GjCLVzlCwu8TFdRZIqVLzWbzNcmkmhfqKEr4TLz4HQ== + dependencies: + "@babel/runtime" "^7.23.2" + +i18next-fs-backend@^2.2.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/i18next-fs-backend/-/i18next-fs-backend-2.6.0.tgz#7b6b54c5ffc2a5073e47eda0673c002376fa1a3c" + integrity sha512-3ZlhNoF9yxnM8pa8bWp5120/Ob6t4lVl1l/tbLmkml/ei3ud8IWySCHt2lrY5xWRlSU5D9IV2sm5bEbGuTqwTw== + +i18next-http-backend@^2.2.2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-2.7.1.tgz#c754e51c242e8029445263d4351096c67feea102" + integrity sha512-vPksHIckysGgykCD8JwCr2YsJEml9Cyw+Yu2wtb4fQ7xIn9RH/hkUDh5UkwnIzb0kSL4SJ30Ab/sCInhQxbCgg== + dependencies: + cross-fetch "4.0.0" + +i18next@^23.5.1: + version "23.16.8" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.16.8.tgz#3ae1373d344c2393f465556f394aba5a9233b93a" + integrity sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg== + dependencies: + "@babel/runtime" "^7.23.2" + iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -12568,6 +12606,11 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +intl-parse-accept-language@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/intl-parse-accept-language/-/intl-parse-accept-language-1.0.0.tgz#204d1bc0b13c5baea7775f5eeb95bef2b795675c" + integrity sha512-YFMSV91JNBOSjw1cOfw2tup6hDP7mkz+2AUV7W1L1AM6ntgI75qC1ZeFpjPGMrWp+upmBRTX2fJWQ8c7jsUWpA== + invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -13720,7 +13763,7 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: +lru-cache@^7.14.1, lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: version "7.18.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== @@ -16923,6 +16966,14 @@ react-hook-form@^7.54.2: resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.54.2.tgz#8c26ed54c71628dff57ccd3c074b1dd377cfb211" integrity sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg== +react-i18next@^13.2.2: + version "13.5.0" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-13.5.0.tgz#44198f747628267a115c565f0c736a50a76b1ab0" + integrity sha512-CFJ5NDGJ2MUyBohEHxljOq/39NQ972rh1ajnadG9BjTk+UXbHLq4z5DKEbEQBDoIhUmmbuS/fIMJKo6VOax1HA== + dependencies: + "@babel/runtime" "^7.22.5" + html-parse-stringify "^3.0.1" + react-icons@^5.4.0: version "5.4.0" resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.4.0.tgz#443000f6e5123ee1b21ea8c0a716f6e7797f7416" @@ -17411,6 +17462,16 @@ remedial@^1.0.7: resolved "https://registry.yarnpkg.com/remedial/-/remedial-1.0.8.tgz#a5e4fd52a0e4956adbaf62da63a5a46a78c578a0" integrity sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg== +remix-i18next@^5.4.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/remix-i18next/-/remix-i18next-5.5.0.tgz#2f545a7807b1f666a4d30487d82e6e2701237b71" + integrity sha512-QAHYlwb/0fmoSmH+t7AiJ3EhyG4SSQNQnAAPfqbkn/3UJfWO+NFFVxuggGx25FRJ3SOPexaspoiaCddcEayPAQ== + dependencies: + accept-language-parser "^1.5.0" + intl-parse-accept-language "^1.0.0" + lru-cache "^7.14.1" + use-consistent-value "^1.0.0" + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -18406,7 +18467,7 @@ string-hash@^1.1.3: resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" integrity sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -18423,15 +18484,6 @@ string-width@^2.1.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -18540,7 +18592,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -18554,13 +18606,6 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -19624,6 +19669,13 @@ urlpattern-polyfill@^10.0.0: resolved "https://registry.yarnpkg.com/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz#f0a03a97bfb03cdf33553e5e79a2aadd22cac8ec" integrity sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg== +use-consistent-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/use-consistent-value/-/use-consistent-value-1.0.0.tgz#6b9143de2f94524cbc41f0e315822a08ed7a8d45" + integrity sha512-enIOysu0IwqjafsUXvhZPajB6UYjxwu8w38xWaHLfVs1onIyg2c0DwPgcknxqO0TpYpAHXdFDXOp7Cs9HbTIkw== + dependencies: + fast-deep-equal "^3.1.3" + use-sync-external-store@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" @@ -19826,6 +19878,11 @@ vite@^5.0.0, vite@^5.0.11, vite@^5.3.6: optionalDependencies: fsevents "~2.3.3" +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + watchpack@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" @@ -20298,7 +20355,7 @@ workerpool@^6.5.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -20316,15 +20373,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 76f3169ec31fce1ed44a5c0331d8528830fca581 Mon Sep 17 00:00:00 2001 From: mashharuki Date: Wed, 1 Jan 2025 20:55:19 +0900 Subject: [PATCH 2/3] =?UTF-8?q?i18n=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=81=A8?= =?UTF-8?q?=E3=82=B5=E3=83=B3=E3=83=97=E3=83=AB=E3=83=9A=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=81=AE=E5=AE=9F=E8=A3=85=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkgs/frontend/app/config/i18n.server.ts | 24 ++++ pkgs/frontend/app/config/i18n.ts | 32 +++-- pkgs/frontend/app/config/i18next.server.ts | 38 ------ pkgs/frontend/app/entry.client.tsx | 43 +++--- pkgs/frontend/app/entry.server.tsx | 140 ++++++++++++++------ pkgs/frontend/app/locales/en.ts | 3 + pkgs/frontend/app/locales/ja.ts | 3 + pkgs/frontend/app/root.tsx | 22 ++- pkgs/frontend/app/routes/api_.locales.ts | 48 +++++++ pkgs/frontend/app/routes/i18n.tsx | 41 ++++++ pkgs/frontend/package.json | 17 +-- pkgs/frontend/public/locales/en/common.json | 3 - pkgs/frontend/public/locales/ja/common.json | 3 - pkgs/frontend/vite.config.ts | 16 ++- yarn.lock | 136 ++++++++++--------- 15 files changed, 369 insertions(+), 200 deletions(-) create mode 100644 pkgs/frontend/app/config/i18n.server.ts delete mode 100644 pkgs/frontend/app/config/i18next.server.ts create mode 100644 pkgs/frontend/app/locales/en.ts create mode 100644 pkgs/frontend/app/locales/ja.ts create mode 100644 pkgs/frontend/app/routes/api_.locales.ts create mode 100644 pkgs/frontend/app/routes/i18n.tsx delete mode 100644 pkgs/frontend/public/locales/en/common.json delete mode 100644 pkgs/frontend/public/locales/ja/common.json diff --git a/pkgs/frontend/app/config/i18n.server.ts b/pkgs/frontend/app/config/i18n.server.ts new file mode 100644 index 0000000..828840f --- /dev/null +++ b/pkgs/frontend/app/config/i18n.server.ts @@ -0,0 +1,24 @@ +import { createCookie } from "@remix-run/node"; +import { RemixI18Next } from "remix-i18next/server"; + +import * as i18n from "~/config/i18n"; + +export const localeCookie = createCookie("lng", { + path: "/", + sameSite: "lax", + secure: process.env.NODE_ENV === "production", + httpOnly: true, +}); + +export default new RemixI18Next({ + detection: { + supportedLanguages: i18n.supportedLngs, + fallbackLanguage: i18n.fallbackLng, + cookie: localeCookie, + }, + // This is the configuration for i18next used + // when translating messages server-side only + i18next: { + ...i18n, + }, +}); diff --git a/pkgs/frontend/app/config/i18n.ts b/pkgs/frontend/app/config/i18n.ts index 0975fe5..082ad11 100644 --- a/pkgs/frontend/app/config/i18n.ts +++ b/pkgs/frontend/app/config/i18n.ts @@ -1,12 +1,20 @@ -import type { InitOptions } from "i18next"; - -export const i18nConfig = { - // Supported languages. - supportedLngs: ["en", "ja"], - // Fallback language. - fallbackLng: "en", - // Default namespace. - defaultNS: "common", - // Disable suspense mode. (Recommended). - react: { useSuspense: false }, -} satisfies InitOptions; +import { serverOnly$ } from "vite-env-only/macros"; + +import enTranslation from "~/locales/en"; +import jaTranslation from "~/locales/ja"; + +// This is the list of languages your application supports, the last one is your +// fallback language +export const supportedLngs = ["en", "ja"]; + +// This is the language you want to use in case +// the user language is not in the supportedLngs +export const fallbackLng = "en"; + +// The default namespace of i18next is "translation", but you can customize it +export const defaultNS = "translation"; + +export const resources = serverOnly$({ + en: { translation: enTranslation }, + ja: { translation: jaTranslation }, +}); diff --git a/pkgs/frontend/app/config/i18next.server.ts b/pkgs/frontend/app/config/i18next.server.ts deleted file mode 100644 index 903be4c..0000000 --- a/pkgs/frontend/app/config/i18next.server.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { createCookie } from "@remix-run/node"; -import { resolve } from "node:path"; - -import { RemixI18Next } from "remix-i18next"; -import { i18nConfig } from "~/config/i18n.js"; - -import Backend from "i18next-fs-backend"; - -export const i18nCookie = createCookie("_i18n", { - path: "/", - sameSite: "lax", - httpOnly: true, - maxAge: 60, // 1 minute - We do not require a long session for i18n. -}); - -export const remixI18Next = new RemixI18Next({ - detection: { - // The cookie used to store user's language preference. - cookie: i18nCookie, - // The supported languages. - supportedLanguages: i18nConfig.supportedLngs, - // The fallback language. - fallbackLanguage: i18nConfig.fallbackLng, - }, - - // The i18next configuration used when translating - // Server-Side only messages. - i18next: { - ...i18nConfig, - backend: { - loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json"), - }, - }, - - // The plugins you want RemixI18next to use for `i18n.getFixedT` inside loaders and actions. - // E.g. The Backend plugin for loading translations from the file system. - plugins: [Backend], -}); diff --git a/pkgs/frontend/app/entry.client.tsx b/pkgs/frontend/app/entry.client.tsx index b479124..a1774e2 100644 --- a/pkgs/frontend/app/entry.client.tsx +++ b/pkgs/frontend/app/entry.client.tsx @@ -1,36 +1,39 @@ import { RemixBrowser } from "@remix-run/react"; +import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector"; +import Fetch from "i18next-fetch-backend"; import { StrictMode, startTransition } from "react"; import { hydrateRoot } from "react-dom/client"; +import { I18nextProvider, initReactI18next } from "react-i18next"; +import { getInitialNamespaces } from "remix-i18next/client"; +import { defaultNS, fallbackLng, supportedLngs } from "~/config/i18n"; import { ChakraProvider } from "./components/chakra-provider"; import { ClientCacheProvider } from "./emotion/emotion-client"; -import { I18nextProvider, initReactI18next } from "react-i18next"; -import { getInitialNamespaces } from "remix-i18next"; -import { i18nConfig } from "~/config/i18n.js"; - import i18next from "i18next"; -import LanguageDetector from "i18next-browser-languagedetector"; -import Backend from "i18next-http-backend"; const hydrate = async () => { await i18next - // Use the react-i18next plugin. - .use(initReactI18next) - // Setup client-side language detector. - .use(LanguageDetector) - // Setup backend. - .use(Backend) + .use(initReactI18next) // Tell i18next to use the react-i18next plugin + .use(Fetch) // Tell i18next to use the Fetch backend + .use(I18nextBrowserLanguageDetector) // Setup a client-side language detector .init({ - // Spread configuration. - ...i18nConfig, - // Detects the namespaces your routes rendered while SSR use - // and pass them here to load the translations. + defaultNS, + fallbackLng, + supportedLngs, ns: getInitialNamespaces(), - backend: { loadPath: "/locales/{{lng}}/{{ns}}.json" }, detection: { - // We'll detect the language only server-side with remix-i18next. - // By using `` attribute we communicate to the Client. + // Here only enable htmlTag detection, we'll detect the language only + // server-side with remix-i18next, by using the `` attribute + // we can communicate to the client the language detected server-side order: ["htmlTag"], + // Because we only use htmlTag, there's no reason to cache the language + // on the browser, so we disable it + caches: [], + }, + backend: { + // We will configure the backend to fetch the translations from the + // resource route /api/locales and pass the lng and ns as search params + loadPath: "/api/locales?lng={{lng}}&ns={{ns}}", }, }); @@ -57,3 +60,5 @@ if (typeof requestIdleCallback === "function") { // https://caniuse.com/requestidlecallback setTimeout(hydrate, 1); } + +hydrate().catch((error) => console.error(error)); diff --git a/pkgs/frontend/app/entry.server.tsx b/pkgs/frontend/app/entry.server.tsx index c461e0e..8ddf016 100644 --- a/pkgs/frontend/app/entry.server.tsx +++ b/pkgs/frontend/app/entry.server.tsx @@ -1,50 +1,64 @@ -import type { EntryContext } from "@remix-run/node"; -import { RemixServer } from "@remix-run/react"; +import { PassThrough } from "node:stream"; +import type { AppLoadContext, EntryContext } from "@remix-run/node"; import { createReadableStreamFromReadable } from "@remix-run/node"; +import { RemixServer } from "@remix-run/react"; +import { createInstance, i18n as i18next } from "i18next"; import { isbot } from "isbot"; -import { resolve } from "node:path"; -import { PassThrough } from "node:stream"; import { renderToPipeableStream } from "react-dom/server"; - -import { createInstance } from "i18next"; -import Backend from "i18next-fs-backend"; import { I18nextProvider, initReactI18next } from "react-i18next"; -import { i18nConfig } from "~/config/i18n.js"; -import { remixI18Next } from "~/config/i18next.server.js"; +import * as i18n from "./config/i18n"; +import i18nServer from "./config/i18n.server"; const ABORT_DELAY = 5_000; -const handleRequest = async ( +export default async function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, - remixContext: EntryContext -) => { - const callbackName = isbot(request.headers.get("user-agent")) - ? "onAllReady" - : "onShellReady"; + remixContext: EntryContext, + // This is ignored so we can keep it in the template for visibility. Feel + // free to delete this parameter in your app if you're not using it! + // eslint-disable-next-line @typescript-eslint/no-unused-vars + loadContext: AppLoadContext +) { + const instance = createInstance(); + const lng = await i18nServer.getLocale(request); + const ns = i18nServer.getRouteNamespaces(remixContext); - // Internationalization (i18n). - const i18nInstance = createInstance(); - const lng = await remixI18Next.getLocale(request); - const ns = remixI18Next.getRouteNamespaces(remixContext); + await instance.use(initReactI18next).init({ ...i18n, lng, ns }); - await i18nInstance - .use(initReactI18next) // Tell our instance to use react-i18next. - .use(Backend) // Setup backend. - .init({ - ...i18nConfig, // Spread configuration. - lng, // Locale detected above. - ns, // Namespaces detected above. - backend: { loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json") }, - }); + return isbot(request.headers.get("user-agent") || "") + ? handleBotRequest( + request, + responseStatusCode, + responseHeaders, + remixContext, + loadContext, + instance + ) + : handleBrowserRequest( + request, + responseStatusCode, + responseHeaders, + remixContext, + loadContext, + instance + ); +} - new Promise((resolve, reject) => { +async function handleBotRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, + _loadContext: AppLoadContext, + i18next: i18next +) { + return new Promise((resolve, reject) => { let shellRendered = false; - const { pipe, abort } = renderToPipeableStream( - + , { - [callbackName]: () => { + onAllReady() { shellRendered = true; const body = new PassThrough(); const stream = createReadableStreamFromReadable(body); @@ -73,9 +87,9 @@ const handleRequest = async ( }, onError(error: unknown) { responseStatusCode = 500; - // Log streaming rendering errors from inside the shell. - // Don't log errors encountered during initial shell rendering, - // since they'll reject and get logged in handleDocumentRequest. + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. if (shellRendered) { console.error(error); } @@ -85,6 +99,58 @@ const handleRequest = async ( setTimeout(abort, ABORT_DELAY); }); -}; +} + +async function handleBrowserRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, + _loadContext: AppLoadContext, + i18next: i18next +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + + + , + { + onShellReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); -export default handleRequest; + responseHeaders.set("Content-Type", "text/html"); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }) + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + } + ); + + setTimeout(abort, ABORT_DELAY); + }); +} diff --git a/pkgs/frontend/app/locales/en.ts b/pkgs/frontend/app/locales/en.ts new file mode 100644 index 0000000..c7d8557 --- /dev/null +++ b/pkgs/frontend/app/locales/en.ts @@ -0,0 +1,3 @@ +export default { + title: "remix-i18n is awesome", +}; diff --git a/pkgs/frontend/app/locales/ja.ts b/pkgs/frontend/app/locales/ja.ts new file mode 100644 index 0000000..2410224 --- /dev/null +++ b/pkgs/frontend/app/locales/ja.ts @@ -0,0 +1,3 @@ +export default { + title: "remix-i18n は凄い!", +}; diff --git a/pkgs/frontend/app/root.tsx b/pkgs/frontend/app/root.tsx index 019d9fb..bd8fa4a 100644 --- a/pkgs/frontend/app/root.tsx +++ b/pkgs/frontend/app/root.tsx @@ -1,5 +1,6 @@ import { withEmotionCache } from "@emotion/react"; import { + json, Links, Meta, Outlet, @@ -12,28 +13,21 @@ import { Box, Container } from "@chakra-ui/react"; import { PrivyProvider } from "@privy-io/react-auth"; import type { LoaderFunctionArgs } from "@remix-run/node"; import { goldskyClient } from "utils/apollo"; -import { i18nCookie, remixI18Next } from "~/config/i18next.server"; import { ChakraProvider } from "./components/chakra-provider"; import { Header } from "./components/Header"; +import i18nServer, { localeCookie } from "./config/i18n.server"; import { useInjectStyles } from "./emotion/emotion-client"; interface LayoutProps extends React.PropsWithChildren {} -export const handle = { - // In the handle export, we can specify i18n namespaces needed for the route. - // Usually, we'll set it to our default namespace or "translation" if haven't set one. - // It can be a string or an array. - i18n: "common", -}; +export const handle = { i18n: ["translation"] }; export async function loader({ request }: LoaderFunctionArgs) { - const locale = await remixI18Next.getLocale(request); - - return Response.json({ locale } as const, { - headers: { - "set-cookie": await i18nCookie.serialize(locale), - }, - }); + const locale = await i18nServer.getLocale(request); + return json( + { locale }, + { headers: { "Set-Cookie": await localeCookie.serialize(locale) } } + ); } export const Layout = withEmotionCache((props: LayoutProps, cache) => { diff --git a/pkgs/frontend/app/routes/api_.locales.ts b/pkgs/frontend/app/routes/api_.locales.ts new file mode 100644 index 0000000..0ee46f0 --- /dev/null +++ b/pkgs/frontend/app/routes/api_.locales.ts @@ -0,0 +1,48 @@ +import { type LoaderFunctionArgs } from "@remix-run/node"; +import { cacheHeader } from "pretty-cache-header"; +import { z } from "zod"; +import { resources } from "~/config/i18n"; + +export async function loader({ request }: LoaderFunctionArgs) { + const url = new URL(request.url); + + // `resources` is only available server-side, but TS doesn't know so we have + // to assert it's not undefined here and assign it to another constant to + // avoid TS errors below + const languages = resources!; + + const lng = z + .string() + .refine((lng): lng is keyof typeof languages => + Object.keys(languages).includes(lng) + ) + .parse(url.searchParams.get("lng")); + + const namespaces = languages[lng]; + + const ns = z + .string() + .refine((ns): ns is keyof typeof namespaces => { + return Object.keys(languages[lng]).includes(ns); + }) + .parse(url.searchParams.get("ns")); + + const headers = new Headers(); + + // On production, we want to add cache headers to the response + if (process.env.NODE_ENV === "production") { + headers.set( + "Cache-Control", + cacheHeader({ + maxAge: "5m", // Cache in the browser for 5 minutes + sMaxage: "1d", // Cache in the CDN for 1 day + // Serve stale content while revalidating for 7 days + staleWhileRevalidate: "7d", + // Serve stale content if there's an error for 7 days + staleIfError: "7d", + }) + ); + } + + return Response.json(namespaces[ns], { headers }); +} diff --git a/pkgs/frontend/app/routes/i18n.tsx b/pkgs/frontend/app/routes/i18n.tsx new file mode 100644 index 0000000..e5fdba4 --- /dev/null +++ b/pkgs/frontend/app/routes/i18n.tsx @@ -0,0 +1,41 @@ +import { Button } from "@chakra-ui/react"; +import { + json, + type LoaderFunctionArgs, + type MetaFunction, +} from "@remix-run/node"; +import { Form } from "@remix-run/react"; +import { useTranslation } from "react-i18next"; +import i18nServer from "~/config/i18n.server"; + +export const meta: MetaFunction = ({ data }) => { + return [{ title: data?.title }]; +}; + +export async function loader({ request }: LoaderFunctionArgs) { + const t = await i18nServer.getFixedT(request); + return json({ title: t("title") }); +} + +/** + * i18nの機能を試すためのサンプルコンポーネント + * @returns + */ +export default function I18n() { + const { t } = useTranslation(); + + return ( +
+

{t("title")}

+ +
+ + +
+
+ ); +} diff --git a/pkgs/frontend/package.json b/pkgs/frontend/package.json index 3e28d9e..8b47dd0 100644 --- a/pkgs/frontend/package.json +++ b/pkgs/frontend/package.json @@ -31,7 +31,6 @@ "axios": "^1.7.9", "dayjs": "^1.11.13", "graphql": "^16.10.0", - "isbot": "^5.1.11", "namestone-sdk": "^0.2.11", "next-themes": "^0.4.3", "permissionless": "^0.2.20", @@ -40,12 +39,13 @@ "react-hook-form": "^7.54.2", "react-icons": "^5.4.0", "viem": "^2.21.51", - "i18next": "^23.5.1", - "i18next-browser-languagedetector": "^7.1.0", - "i18next-fs-backend": "^2.2.0", - "i18next-http-backend": "^2.2.2", - "react-i18next": "^13.2.2", - "remix-i18next": "^5.4.0" + "i18next": "^23.12.2", + "i18next-browser-languagedetector": "^8.0.0", + "i18next-fetch-backend": "^6.0.0", + "react-i18next": "^14.1.3", + "remix-i18next": "^6.1.0", + "isbot": "^5.1.13", + "pretty-cache-header": "^1.0.0" }, "devDependencies": { "@graphql-codegen/cli": "5.0.3", @@ -68,7 +68,8 @@ "prettier": "^3.3.3", "typescript": "^5.6.2", "vite": "^5.3.6", - "vite-tsconfig-paths": "^4.3.2" + "vite-tsconfig-paths": "^4.3.2", + "vite-env-only": "^3.0.3" }, "engines": { "node": ">=20.0.0" diff --git a/pkgs/frontend/public/locales/en/common.json b/pkgs/frontend/public/locales/en/common.json deleted file mode 100644 index ad06d0b..0000000 --- a/pkgs/frontend/public/locales/en/common.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "remix-i18n is awesome" -} diff --git a/pkgs/frontend/public/locales/ja/common.json b/pkgs/frontend/public/locales/ja/common.json deleted file mode 100644 index a0d4cb6..0000000 --- a/pkgs/frontend/public/locales/ja/common.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "remix-i18n は凄い!" -} diff --git a/pkgs/frontend/vite.config.ts b/pkgs/frontend/vite.config.ts index 152fb04..162b4cd 100644 --- a/pkgs/frontend/vite.config.ts +++ b/pkgs/frontend/vite.config.ts @@ -1,9 +1,15 @@ import { vitePlugin as remix } from "@remix-run/dev"; +import { installGlobals } from "@remix-run/node"; import { defineConfig } from "vite"; +import { envOnlyMacros } from "vite-env-only"; import tsconfigPaths from "vite-tsconfig-paths"; +installGlobals(); + export default defineConfig({ plugins: [ + envOnlyMacros(), + tsconfigPaths(), remix({ future: { v3_fetcherPersist: true, @@ -12,11 +18,19 @@ export default defineConfig({ }, ssr: true, }), - tsconfigPaths(), ], server: { hmr: { overlay: false, }, }, + build: { + rollupOptions: { + external: [ + "remix-i18next/client", + "remix-i18next", + "remix-i18next/server", + ], + }, + }, }); diff --git a/yarn.lock b/yarn.lock index 132834b..eb696c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -462,7 +462,7 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e" integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== -"@babel/core@^7.14.0", "@babel/core@^7.20.7", "@babel/core@^7.21.3", "@babel/core@^7.21.8", "@babel/core@^7.22.9", "@babel/core@^7.23.9", "@babel/core@^7.25.9": +"@babel/core@^7.14.0", "@babel/core@^7.20.7", "@babel/core@^7.21.3", "@babel/core@^7.21.8", "@babel/core@^7.22.9", "@babel/core@^7.23.7", "@babel/core@^7.23.9", "@babel/core@^7.25.9": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== @@ -483,7 +483,7 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.14.0", "@babel/generator@^7.18.13", "@babel/generator@^7.26.3": +"@babel/generator@^7.14.0", "@babel/generator@^7.18.13", "@babel/generator@^7.23.6", "@babel/generator@^7.26.3": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.3.tgz#ab8d4360544a425c90c248df7059881f4b2ce019" integrity sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ== @@ -667,7 +667,7 @@ "@babel/template" "^7.25.9" "@babel/types" "^7.26.0" -"@babel/parser@^7.14.0", "@babel/parser@^7.16.8", "@babel/parser@^7.26.3": +"@babel/parser@^7.14.0", "@babel/parser@^7.16.8", "@babel/parser@^7.23.6", "@babel/parser@^7.26.3": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.3.tgz#8c51c5db6ddf08134af1ddbacf16aaab48bac234" integrity sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA== @@ -1398,7 +1398,7 @@ core-js-pure "^3.30.2" regenerator-runtime "^0.14.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.25.0", "@babel/runtime@^7.25.9", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.9", "@babel/runtime@^7.25.0", "@babel/runtime@^7.25.9", "@babel/runtime@^7.8.4": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== @@ -1414,7 +1414,7 @@ "@babel/parser" "^7.25.9" "@babel/types" "^7.25.9" -"@babel/traverse@^7.14.0", "@babel/traverse@^7.16.8": +"@babel/traverse@^7.14.0", "@babel/traverse@^7.16.8", "@babel/traverse@^7.23.7": version "7.26.4" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd" integrity sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w== @@ -1440,7 +1440,7 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.16.8", "@babel/types@^7.18.13", "@babel/types@^7.26.3": +"@babel/types@^7.0.0", "@babel/types@^7.16.8", "@babel/types@^7.18.13", "@babel/types@^7.23.6", "@babel/types@^7.26.3": version "7.26.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== @@ -7323,11 +7323,6 @@ abortcontroller-polyfill@^1.7.5: resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.6.tgz#7be8d35b5ed7dfa1a51b36f221720b23deb13f36" integrity sha512-Zypm+LjYdWAzvuypZvDN0smUJrhOurcuBWhhMRBExqVLRvdjp3Z9mASxKyq19K+meZMshwjjy5S0lkm388zE4Q== -accept-language-parser@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/accept-language-parser/-/accept-language-parser-1.5.0.tgz#8877c54040a8dcb59e0a07d9c1fde42298334791" - integrity sha512-QhyTbMLYo0BBGg1aWbeMG4ekWtds/31BrEU+DONOg/7ax23vxpL03Pb7/zBmha2v7vdD3AyzZVWBVGEZxKOXWw== - accepts@~1.3.4, accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -7825,6 +7820,16 @@ axobject-query@^4.1.0: resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== +babel-dead-code-elimination@^1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.8.tgz#d8d14a7da8dc1650238834c2395594b77b716099" + integrity sha512-og6HQERk0Cmm+nTT4Od2wbPtgABXFMPaHACjbKLulZIFMkYyXZLkUGuAxdgpMJBrxyt/XFpSz++lNzjbcMnPkQ== + dependencies: + "@babel/core" "^7.23.7" + "@babel/parser" "^7.23.6" + "@babel/traverse" "^7.23.7" + "@babel/types" "^7.23.6" + babel-loader@^9.2.1: version "9.2.1" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-9.2.1.tgz#04c7835db16c246dd19ba0914418f3937797587b" @@ -9085,13 +9090,6 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-fetch@4.0.0, cross-fetch@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" - integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== - dependencies: - node-fetch "^2.6.12" - cross-fetch@^3.1.4, cross-fetch@^3.1.5: version "3.1.8" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" @@ -9099,6 +9097,13 @@ cross-fetch@^3.1.4, cross-fetch@^3.1.5: dependencies: node-fetch "^2.6.12" +cross-fetch@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" + integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== + dependencies: + node-fetch "^2.6.12" + cross-inspect@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cross-inspect/-/cross-inspect-1.0.1.tgz#15f6f65e4ca963cf4cc1a2b5fef18f6ca328712b" @@ -12413,26 +12418,19 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -i18next-browser-languagedetector@^7.1.0: - version "7.2.2" - resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.2.tgz#748e7dc192847613911d8a79d9d9a6c2d266133e" - integrity sha512-6b7r75uIJDWCcCflmbof+sJ94k9UQO4X0YR62oUfqGI/GjCLVzlCwu8TFdRZIqVLzWbzNcmkmhfqKEr4TLz4HQ== +i18next-browser-languagedetector@^8.0.0: + version "8.0.2" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.2.tgz#037ca25c26877cad778f060a9e177054d9f8eaa3" + integrity sha512-shBvPmnIyZeD2VU5jVGIOWP7u9qNG3Lj7mpaiPFpbJ3LVfHZJvVzKR4v1Cb91wAOFpNw442N+LGPzHOHsten2g== dependencies: "@babel/runtime" "^7.23.2" -i18next-fs-backend@^2.2.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/i18next-fs-backend/-/i18next-fs-backend-2.6.0.tgz#7b6b54c5ffc2a5073e47eda0673c002376fa1a3c" - integrity sha512-3ZlhNoF9yxnM8pa8bWp5120/Ob6t4lVl1l/tbLmkml/ei3ud8IWySCHt2lrY5xWRlSU5D9IV2sm5bEbGuTqwTw== - -i18next-http-backend@^2.2.2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-2.7.1.tgz#c754e51c242e8029445263d4351096c67feea102" - integrity sha512-vPksHIckysGgykCD8JwCr2YsJEml9Cyw+Yu2wtb4fQ7xIn9RH/hkUDh5UkwnIzb0kSL4SJ30Ab/sCInhQxbCgg== - dependencies: - cross-fetch "4.0.0" +i18next-fetch-backend@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/i18next-fetch-backend/-/i18next-fetch-backend-6.0.0.tgz#21bfedc2a98f7540dd39aad2bab3ca4cdcca534c" + integrity sha512-kVqnydqLVMZfVlOuP2nf71cREydlxEKLH43jUXAFdOku/GF+6b9fBg31anoos5XncdhdtiYgL9fheqMrtXRwng== -i18next@^23.5.1: +i18next@^23.12.2: version "23.16.8" resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.16.8.tgz#3ae1373d344c2393f465556f394aba5a9233b93a" integrity sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg== @@ -12606,11 +12604,6 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== -intl-parse-accept-language@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/intl-parse-accept-language/-/intl-parse-accept-language-1.0.0.tgz#204d1bc0b13c5baea7775f5eeb95bef2b795675c" - integrity sha512-YFMSV91JNBOSjw1cOfw2tup6hDP7mkz+2AUV7W1L1AM6ntgI75qC1ZeFpjPGMrWp+upmBRTX2fJWQ8c7jsUWpA== - invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -13111,10 +13104,10 @@ isarray@^2.0.5: resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== -isbot@^5.1.11: - version "5.1.17" - resolved "https://registry.yarnpkg.com/isbot/-/isbot-5.1.17.tgz#ad7da5690a61bbb19056a069975c9a73182682a0" - integrity sha512-/wch8pRKZE+aoVhRX/hYPY1C7dMCeeMyhkQLNLNlYAbGQn9bkvMB8fOUXNnk5I0m4vDYbBJ9ciVtkr9zfBJ7qA== +isbot@^5.1.13: + version "5.1.19" + resolved "https://registry.yarnpkg.com/isbot/-/isbot-5.1.19.tgz#0ce534e08f4697992a429fa5bf5504015388f681" + integrity sha512-8krWJBGKC3lVymkncvmBTpIEWMD5kKmjAvkM3/Xh6veE0bAydwgSNrI5h493DGrG2UNJCy0HuHpNPSKRy0dBJA== isexe@^2.0.0: version "2.0.0" @@ -13763,7 +13756,7 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -lru-cache@^7.14.1, lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: +lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: version "7.18.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== @@ -16586,6 +16579,13 @@ prettier@^3.3.3: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.1.tgz#e211d451d6452db0a291672ca9154bc8c2579f7b" integrity sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg== +pretty-cache-header@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pretty-cache-header/-/pretty-cache-header-1.0.0.tgz#66d400a6c2990833a3baeca9d9fcdd7e50587b7a" + integrity sha512-xtXazslu25CdnGnUkByU1RoOjK55TqwatJkjjJLg5ZAdz2Lngko/mmaUgeET36P2GMlNwh3fdM7FWBO717pNcw== + dependencies: + timestring "^6.0.0" + pretty-error@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" @@ -16966,12 +16966,12 @@ react-hook-form@^7.54.2: resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.54.2.tgz#8c26ed54c71628dff57ccd3c074b1dd377cfb211" integrity sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg== -react-i18next@^13.2.2: - version "13.5.0" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-13.5.0.tgz#44198f747628267a115c565f0c736a50a76b1ab0" - integrity sha512-CFJ5NDGJ2MUyBohEHxljOq/39NQ972rh1ajnadG9BjTk+UXbHLq4z5DKEbEQBDoIhUmmbuS/fIMJKo6VOax1HA== +react-i18next@^14.1.3: + version "14.1.3" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-14.1.3.tgz#85525c4294ef870ddd3f5d184e793cae362f47cb" + integrity sha512-wZnpfunU6UIAiJ+bxwOiTmBOAaB14ha97MjOEnLGac2RJ+h/maIYXZuTHlmyqQVX1UVHmU1YDTQ5vxLmwfXTjw== dependencies: - "@babel/runtime" "^7.22.5" + "@babel/runtime" "^7.23.9" html-parse-stringify "^3.0.1" react-icons@^5.4.0: @@ -17462,15 +17462,10 @@ remedial@^1.0.7: resolved "https://registry.yarnpkg.com/remedial/-/remedial-1.0.8.tgz#a5e4fd52a0e4956adbaf62da63a5a46a78c578a0" integrity sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg== -remix-i18next@^5.4.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/remix-i18next/-/remix-i18next-5.5.0.tgz#2f545a7807b1f666a4d30487d82e6e2701237b71" - integrity sha512-QAHYlwb/0fmoSmH+t7AiJ3EhyG4SSQNQnAAPfqbkn/3UJfWO+NFFVxuggGx25FRJ3SOPexaspoiaCddcEayPAQ== - dependencies: - accept-language-parser "^1.5.0" - intl-parse-accept-language "^1.0.0" - lru-cache "^7.14.1" - use-consistent-value "^1.0.0" +remix-i18next@^6.1.0: + version "6.4.1" + resolved "https://registry.yarnpkg.com/remix-i18next/-/remix-i18next-6.4.1.tgz#242ddc8c65d2ed7127ab06b415d865a84d68fd8e" + integrity sha512-Ma4ESNj8uZmm/Fjq2ZHEbsGvYYO2rV1BtqwIFPAYUeitBaED+r0pNXktUzvsHEanvbFOZPAhEcp5f17vDslmeA== remove-trailing-separator@^1.0.1: version "1.1.0" @@ -18943,6 +18938,11 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== +timestring@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/timestring/-/timestring-6.0.0.tgz#b0c7c331981ecf2066ce88bcfb8ee3ae32e7a0f6" + integrity sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA== + tiny-invariant@^1.0.2: version "1.3.3" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" @@ -19669,13 +19669,6 @@ urlpattern-polyfill@^10.0.0: resolved "https://registry.yarnpkg.com/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz#f0a03a97bfb03cdf33553e5e79a2aadd22cac8ec" integrity sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg== -use-consistent-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/use-consistent-value/-/use-consistent-value-1.0.0.tgz#6b9143de2f94524cbc41f0e315822a08ed7a8d45" - integrity sha512-enIOysu0IwqjafsUXvhZPajB6UYjxwu8w38xWaHLfVs1onIyg2c0DwPgcknxqO0TpYpAHXdFDXOp7Cs9HbTIkw== - dependencies: - fast-deep-equal "^3.1.3" - use-sync-external-store@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" @@ -19847,6 +19840,19 @@ viem@^2.20.1, viem@^2.21.15, viem@^2.21.51, viem@^2.21.9: webauthn-p256 "0.0.10" ws "8.18.0" +vite-env-only@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vite-env-only/-/vite-env-only-3.0.3.tgz#cf43be571af1ed6f71d715b51625a81dc7f9d029" + integrity sha512-iAb7cTXRrvFShaF1n+G8f6Yqq7sRJcxipNYNQQu0DN5N9P55vJMmLG5lNU5moYGpd+ZH1WhBHdkWi5WjrfImHg== + dependencies: + "@babel/core" "^7.23.7" + "@babel/generator" "^7.23.6" + "@babel/parser" "^7.23.6" + "@babel/traverse" "^7.23.7" + "@babel/types" "^7.23.6" + babel-dead-code-elimination "^1.0.6" + micromatch "^4.0.5" + vite-node@^1.2.0, vite-node@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.6.0.tgz#2c7e61129bfecc759478fa592754fd9704aaba7f" From ce56c90acdf267c3c0ee4052ee366f370262a3d8 Mon Sep 17 00:00:00 2001 From: mashharuki Date: Wed, 8 Jan 2025 23:47:17 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkgs/frontend/app/entry.client.tsx | 2 ++ pkgs/frontend/app/routes/i18n.tsx | 7 +++---- yarn.lock | 31 +++--------------------------- 3 files changed, 8 insertions(+), 32 deletions(-) diff --git a/pkgs/frontend/app/entry.client.tsx b/pkgs/frontend/app/entry.client.tsx index a1774e2..84ae4bc 100644 --- a/pkgs/frontend/app/entry.client.tsx +++ b/pkgs/frontend/app/entry.client.tsx @@ -53,6 +53,7 @@ const hydrate = async () => { }); }; +/* if (typeof requestIdleCallback === "function") { requestIdleCallback(hydrate); } else { @@ -60,5 +61,6 @@ if (typeof requestIdleCallback === "function") { // https://caniuse.com/requestidlecallback setTimeout(hydrate, 1); } +*/ hydrate().catch((error) => console.error(error)); diff --git a/pkgs/frontend/app/routes/i18n.tsx b/pkgs/frontend/app/routes/i18n.tsx index e5fdba4..f9844d5 100644 --- a/pkgs/frontend/app/routes/i18n.tsx +++ b/pkgs/frontend/app/routes/i18n.tsx @@ -4,8 +4,7 @@ import { type LoaderFunctionArgs, type MetaFunction, } from "@remix-run/node"; -import { Form } from "@remix-run/react"; -import { useTranslation } from "react-i18next"; +import { Form, useLoaderData } from "@remix-run/react"; import i18nServer from "~/config/i18n.server"; export const meta: MetaFunction = ({ data }) => { @@ -22,11 +21,11 @@ export async function loader({ request }: LoaderFunctionArgs) { * @returns */ export default function I18n() { - const { t } = useTranslation(); + const { title } = useLoaderData(); return (
-

{t("title")}

+

{title}