Skip to content

Commit

Permalink
i18nの対応途中。とりあえずプッシュします。
Browse files Browse the repository at this point in the history
  • Loading branch information
mashharuki committed Dec 9, 2024
1 parent dc8f002 commit 3ad9aed
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 51 deletions.
44 changes: 36 additions & 8 deletions pkgs/frontend/app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,48 @@
import { RemixBrowser } from "@remix-run/react";
import i18next from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import Backend from "i18next-http-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 { ChakraProvider } from "./components/chakra-provider";
import { ClientCacheProvider } from "./emotion/emotion-client";
import i18n from "./i18n";

const hydrate = async () => {
await i18next
.use(initReactI18next) // Tell i18next to use the react-i18next plugin
.use(LanguageDetector) // Setup a client-side language detector
.use(Backend) // Setup your backend
.init({
...i18n, // spread the configuration
// This function detects the namespaces your routes rendered while SSR use
ns: getInitialNamespaces(),
backend: { loadPath: "/locales/{{lng}}/{{ns}}.json" },
detection: {
// Here only enable htmlTag detection, we'll detect the language only
// server-side with remix-i18next, by using the `<html lang>` 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: [],
},
});

const hydrate = () => {
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<ClientCacheProvider>
<ChakraProvider>
<RemixBrowser />
</ChakraProvider>
</ClientCacheProvider>
</StrictMode>
<I18nextProvider i18n={i18next}>
<StrictMode>
<ClientCacheProvider>
<ChakraProvider>
<RemixBrowser />
</ChakraProvider>
</ClientCacheProvider>
</StrictMode>
</I18nextProvider>
);
});
};
Expand Down
29 changes: 26 additions & 3 deletions pkgs/frontend/app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
import type { EntryContext } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { createInstance } from "i18next";
import Backend from "i18next-fs-backend";
import { resolve } from "node:path";
import { I18nextProvider, initReactI18next } from "react-i18next";
import { createEmotion } from "./emotion/emotion-server";
import i18n from "./i18n";
import i18next from "./i18next.server";

const handleRequest = (
const handleRequest = async (
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) =>
) => {
const instance = createInstance();
const lng = await i18next.getLocale(request);
const ns = i18next.getRouteNamespaces(remixContext as any);

await instance
.use(initReactI18next) // Tell our instance to use react-i18next
.use(Backend) // Setup our backend
.init({
...i18n, // spread the configuration
lng, // The locale we detected above
ns, // The namespaces the routes about to render wants to use
backend: { loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json") },
});

new Promise((resolve) => {
const { renderToString, injectStyles } = createEmotion();

const html = renderToString(
<RemixServer context={remixContext} url={request.url} />
<I18nextProvider i18n={instance}>
<RemixServer context={remixContext} url={request.url} />
</I18nextProvider>
);

responseHeaders.set("Content-Type", "text/html");
Expand All @@ -24,5 +46,6 @@ const handleRequest = (

resolve(response);
});
};

export default handleRequest;
10 changes: 10 additions & 0 deletions pkgs/frontend/app/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default {
debugger: true,
// This is the list of languages your application supports
supportedLngs: ["en", "ja"],
// This is the language you want to use in case
// if the user language is not in the supportedLngs
fallbackLng: "en",
// The default namespace of i18next is "translation", but you can customize it here
defaultNS: "common",
};
28 changes: 28 additions & 0 deletions pkgs/frontend/app/i18next.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Backend from "i18next-fs-backend";
import { resolve } from "node:path";
import { RemixI18Next } from "remix-i18next/server";
import i18n from "~/i18n"; // your i18n configuration file

/**
* This is the i18next instance that will be used to translate messages server-side
*/
const i18next = new RemixI18Next({
detection: {
supportedLanguages: i18n.supportedLngs,
fallbackLanguage: i18n.fallbackLng,
},
// This is the configuration for i18next used
// when translating messages server-side only
i18next: {
...i18n,
backend: {
loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json"),
},
},
// The i18next 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
// Tip: You could pass `resources` to the `i18next` configuration and avoid a backend here
plugins: [Backend],
});

export default i18next;
35 changes: 31 additions & 4 deletions pkgs/frontend/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,53 @@
import { withEmotionCache } from "@emotion/react";
import {
json,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
} 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 { PrivyProvider } from "@privy-io/react-auth";
import { LoaderFunctionArgs } from "@remix-run/node";
import { useTranslation } from "react-i18next";
import { useChangeLanguage } from "remix-i18next/react";
import i18next from "~/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 async function loader({ request }: LoaderFunctionArgs) {
const locale = await i18next.getLocale(request);
return json({ locale });
}

export const handle = {
// In the handle export, we can add a i18n key with namespaces our route
// will need to load. This key can be a single string or an array of strings.
// TIP: In most cases, you should set this to your defaultNS from your i18n config
// or if you did not set one, set it to the i18next default namespace "translation"
i18n: "common",
};

/**
* Layout
*/
export const Layout = withEmotionCache((props: LayoutProps, cache) => {
const { children } = props;
// Get the locale from the loader
const { locale } = useLoaderData<typeof loader>();
const { i18n } = useTranslation();

useInjectStyles(cache);
useChangeLanguage(locale);

return (
<html lang="en">
<html lang={locale} dir={i18n.dir()}>
<head suppressHydrationWarning>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
10 changes: 10 additions & 0 deletions pkgs/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,22 @@
"@remix-run/serve": "^2.15.0",
"@solana/web3.js": "^1.95.5",
"axios": "^1.7.9",
"i18next": "^24.0.5",
"i18next-browser-languagedetector": "^8.0.2",
"i18next-fs-backend": "^2.6.0",
"i18next-http-backend": "^3.0.1",
"isbot": "^5.1.11",
"lucide-react": "^0.399.0",
"namestone-sdk": "^0.2.11",
"next-themes": "^0.4.3",
"permissionless": "^0.2.20",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^15.1.3",
"react-icons": "^5.4.0",
"react-router": "^7.0.2",
"react-router-dom": "^7.0.2",
"remix-i18next": "^7.0.1",
"viem": "^2.21.51"
},
"devDependencies": {
Expand All @@ -49,8 +57,10 @@
"eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-react": "^7.34.3",
"eslint-plugin-react-hooks": "^4.6.2",
"i18next": "^24.0.5",
"postcss": "^8.4.38",
"prettier": "^3.3.3",
"react-i18next": "^15.1.3",
"typescript": "^5.6.2",
"vite": "^5.3.6",
"vite-tsconfig-paths": "^4.3.2"
Expand Down
3 changes: 3 additions & 0 deletions pkgs/frontend/public/locales/en/common.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"title": "remix-i18n is awesome"
}
3 changes: 3 additions & 0 deletions pkgs/frontend/public/locales/ja/common.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"title": "remix-i18n は凄い!"
}
8 changes: 8 additions & 0 deletions pkgs/frontend/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,12 @@ export default defineConfig({
}),
tsconfigPaths(),
],
server: {
hmr: {
overlay: false, // オーバーレイを無効化
},
},
build: {
target: "es2020",
},
});
Loading

0 comments on commit 3ad9aed

Please sign in to comment.