From ae96919dcf7c2b099528389b07620505c679d407 Mon Sep 17 00:00:00 2001 From: secondl1ght Date: Wed, 3 Jul 2024 21:29:26 -0600 Subject: [PATCH] feat: setup i18n --- .vscode/settings.json | 5 +- global.d.ts | 8 ++++ messages/en.json | 1 + messages/es.json | 1 + next.config.mjs | 6 ++- package-lock.json | 103 ++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/app/layout.tsx | 39 +++++++++------- src/i18n.ts | 32 +++++++++++++ tsconfig.json | 8 +++- 10 files changed, 185 insertions(+), 19 deletions(-) create mode 100644 global.d.ts create mode 100644 messages/en.json create mode 100644 messages/es.json create mode 100644 src/i18n.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index a4d05c41..0dbe9663 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,8 @@ "files.associations": { "*.css": "tailwindcss" }, - "editor.formatOnSave": true + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + } } diff --git a/global.d.ts b/global.d.ts new file mode 100644 index 00000000..b749518b --- /dev/null +++ b/global.d.ts @@ -0,0 +1,8 @@ +import en from './messages/en.json'; + +type Messages = typeof en; + +declare global { + // Use type safe message keys with `next-intl` + interface IntlMessages extends Messages {} +} diff --git a/messages/en.json b/messages/en.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/messages/en.json @@ -0,0 +1 @@ +{} diff --git a/messages/es.json b/messages/es.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/messages/es.json @@ -0,0 +1 @@ +{} diff --git a/next.config.mjs b/next.config.mjs index 86d13ca8..3db59410 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,3 +1,7 @@ +import createNextIntlPlugin from 'next-intl/plugin'; + +const withNextIntl = createNextIntlPlugin(); + /** @type {import('next').NextConfig} */ const nextConfig = { output: 'standalone', @@ -40,4 +44,4 @@ const nextConfig = { }, }; -export default nextConfig; +export default withNextIntl(nextConfig); diff --git a/package-lock.json b/package-lock.json index 8ec9d56a..1a359601 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "lucide-react": "^0.395.0", "lwk_wasm": "^0.6.3", "next": "^14.2.4", + "next-intl": "^3.15.3", "next-qrcode": "^2.5.1", "next-themes": "^0.3.0", "nostr-tools": "^2.7.0", @@ -1864,6 +1865,58 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz", "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==" }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", + "integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==", + "dependencies": { + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", + "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.7.8", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz", + "integrity": "sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/icu-skeleton-parser": "1.8.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz", + "integrity": "sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.2.32", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz", + "integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@graphql-codegen/add": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@graphql-codegen/add/-/add-5.0.2.tgz", @@ -9156,6 +9209,17 @@ "node": ">= 0.4" } }, + "node_modules/intl-messageformat": { + "version": "10.5.14", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.14.tgz", + "integrity": "sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/icu-messageformat-parser": "2.7.8", + "tslib": "^2.4.0" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -10637,6 +10701,14 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next": { "version": "14.2.4", "resolved": "https://registry.npmjs.org/next/-/next-14.2.4.tgz", @@ -10686,6 +10758,26 @@ } } }, + "node_modules/next-intl": { + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-3.15.3.tgz", + "integrity": "sha512-jNc2xYzwv0Q4EQKvuHye9dXaDaneiP/ZCQC+AccyOQD6N9d/FZiSWT4wfVVD4B0IXC1Hhzj1QussUu+k3ynnTg==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "dependencies": { + "@formatjs/intl-localematcher": "^0.2.32", + "negotiator": "^0.6.3", + "use-intl": "^3.15.3" + }, + "peerDependencies": { + "next": "^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/next-qrcode": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/next-qrcode/-/next-qrcode-2.5.1.tgz", @@ -13432,6 +13524,17 @@ } } }, + "node_modules/use-intl": { + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-3.15.3.tgz", + "integrity": "sha512-cHSeFy2cy4u6tT8A7KAcDbs+Hz6lytXClVSsOI1leD6OOrpakNxsmyLa8SMrttOAUQto5kV1f4LVhiX/lpkO3g==", + "dependencies": { + "intl-messageformat": "^10.5.14" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/use-sidecar": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", diff --git a/package.json b/package.json index d47124c4..3f21d876 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "lucide-react": "^0.395.0", "lwk_wasm": "^0.6.3", "next": "^14.2.4", + "next-intl": "^3.15.3", "next-qrcode": "^2.5.1", "next-themes": "^0.3.0", "nostr-tools": "^2.7.0", diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 5f4327e8..3e4b8135 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,8 @@ import './globals.css'; import type { Metadata } from 'next'; import { Noto_Sans } from 'next/font/google'; import { cookies } from 'next/headers'; +import { NextIntlClientProvider } from 'next-intl'; +import { getLocale, getMessages } from 'next-intl/server'; import { Toaster } from '@/components/ui/toaster'; import { ApolloWrapper } from '@/lib/apollo/wrapper'; @@ -17,7 +19,7 @@ export const metadata: Metadata = { description: 'Banco', }; -export default function RootLayout({ +export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; @@ -29,24 +31,29 @@ export default function RootLayout({ const accessToken = cookieStore.get('amboss_banco_access_token')?.value; const refreshToken = cookieStore.get('amboss_banco_refresh_token')?.value; + const locale = await getLocale(); + const messages = await getMessages(); + return ( - + - - + - {children} - - - + + {children} + + + + ); diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 00000000..59dad931 --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,32 @@ +import { cookies, headers } from 'next/headers'; +import { getRequestConfig } from 'next-intl/server'; + +type SupportedLanguage = 'en' | 'es'; +const defaultLocale = 'en'; + +export default getRequestConfig(async () => { + let locale: SupportedLanguage; + + const cookieStore = cookies(); + const localeCookie = cookieStore.get('locale') as + | SupportedLanguage + | undefined; + + const headersList = headers(); + const localeHeader = headersList.get('Accept-Language'); + + if (localeCookie) { + locale = localeCookie; + } else if (localeHeader?.includes('es')) { + locale = 'es'; + } else if (localeHeader?.includes('en')) { + locale = 'en'; + } else { + locale = defaultLocale; + } + + return { + locale, + messages: (await import(`../messages/${locale}.json`)).default, + }; +}); diff --git a/tsconfig.json b/tsconfig.json index 7b285893..a04d5711 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,6 +21,12 @@ "@/*": ["./src/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": [ + "next-env.d.ts", + "global.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], "exclude": ["node_modules"] }