From 20b64ff247092bbcd98fd29ec1f3b38643c3a76c Mon Sep 17 00:00:00 2001 From: DIYgod Date: Wed, 22 Feb 2023 04:05:16 +0000 Subject: [PATCH] feat: init i18n with custom language detector --- next-i18next.config.js | 6 +++ next.config.js | 7 ++++ package.json | 3 ++ pnpm-lock.yaml | 79 +++++++++++++++++++++++++++++++++++ public/locales/en/common.json | 3 ++ public/locales/zh/common.json | 3 ++ src/lib/language-detector.ts | 5 +++ src/pages/_app.tsx | 3 +- src/pages/index.tsx | 7 +++- 9 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 next-i18next.config.js create mode 100644 public/locales/en/common.json create mode 100644 public/locales/zh/common.json create mode 100644 src/lib/language-detector.ts diff --git a/next-i18next.config.js b/next-i18next.config.js new file mode 100644 index 00000000..7cf82e97 --- /dev/null +++ b/next-i18next.config.js @@ -0,0 +1,6 @@ +module.exports = { + i18n: { + defaultLocale: "en", + locales: ["en", "zh"], + }, +} diff --git a/next.config.js b/next.config.js index 4d7c76a8..1341a1fa 100644 --- a/next.config.js +++ b/next.config.js @@ -5,9 +5,11 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({ enabled: process.env.ANALYZE === "true", }) const execSync = require("child_process").execSync +const { i18n } = require("./next-i18next.config") const cache = require("next-pwa/cache") const withPWA = require("next-pwa")({ + disable: process.env.NODE_ENV === "development", dest: "public", publicExcludes: ["*"], runtimeCaching: [ @@ -110,5 +112,10 @@ module.exports = withBundleAnalyzer( ENV_OPENSEA_API_KEY: process.env.OPENSEA_API_KEY, ENV_POAP_API_KEY: process.env.POAP_API_KEY, }, + + i18n: { + ...i18n, + localeDetection: false, + }, }), ) diff --git a/package.json b/package.json index c7af6e4c..1e9ae8ce 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "emoji-mart": "5.5.2", "fast-average-color": "^9.3.0", "fast-deep-equal": "3.1.3", + "i18next": "^22.4.10", "idb-keyval": "6.2.0", "immer": "9.0.19", "ioredis": "5.3.1", @@ -63,6 +64,7 @@ "mdast-util-toc": "6.1.1", "nanoid": "4.0.1", "next": "13.1.6", + "next-i18next": "^13.1.5", "nextjs-progressbar": "0.0.16", "nodejieba": "2.6.0", "pinyin": "3.0.0-alpha.5", @@ -71,6 +73,7 @@ "react-dom": "18.2.0", "react-hook-form": "7.43.1", "react-hot-toast": "2.4.0", + "react-i18next": "^12.1.5", "react-infinite-scroller": "1.2.6", "react-scroll": "1.8.9", "react-sortablejs": "6.1.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c861a6cc..f69c74cf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,7 @@ specifiers: fast-average-color: ^9.3.0 fast-deep-equal: 3.1.3 husky: 8.0.3 + i18next: ^22.4.10 idb-keyval: 6.2.0 immer: 9.0.19 ioredis: 5.3.1 @@ -81,6 +82,7 @@ specifiers: mdast-util-toc: 6.1.1 nanoid: 4.0.1 next: 13.1.6 + next-i18next: ^13.1.5 next-pwa: ^5.6.0 nextjs-progressbar: 0.0.16 nodejieba: 2.6.0 @@ -92,6 +94,7 @@ specifiers: react-dom: 18.2.0 react-hook-form: 7.43.1 react-hot-toast: 2.4.0 + react-i18next: ^12.1.5 react-infinite-scroller: 1.2.6 react-scroll: 1.8.9 react-sortablejs: 6.1.4 @@ -165,6 +168,7 @@ dependencies: emoji-mart: 5.5.2 fast-average-color: 9.3.0 fast-deep-equal: 3.1.3 + i18next: 22.4.10 idb-keyval: 6.2.0 immer: 9.0.19 ioredis: 5.3.1 @@ -174,6 +178,7 @@ dependencies: mdast-util-toc: 6.1.1 nanoid: 4.0.1 next: 13.1.6_biqbaboplfbrettd7655fr4n2y + next-i18next: 13.1.5_ict23jwgb5pzpgamw2nvwotxa4 nextjs-progressbar: 0.0.16_next@13.1.6+react@18.2.0 nodejieba: 2.6.0 pinyin: 3.0.0-alpha.5 @@ -182,6 +187,7 @@ dependencies: react-dom: 18.2.0_react@18.2.0 react-hook-form: 7.43.1_react@18.2.0 react-hot-toast: 2.4.0_biqbaboplfbrettd7655fr4n2y + react-i18next: 12.1.5_3yopsigl4h4eb2nqrqfsy65uwi react-infinite-scroller: 1.2.6_react@18.2.0 react-scroll: 1.8.9_biqbaboplfbrettd7655fr4n2y react-sortablejs: 6.1.4_pkvhgat2fktt3g4eqjjiisgkie @@ -4015,6 +4021,13 @@ packages: dependencies: '@types/unist': 2.0.6 + /@types/hoist-non-react-statics/3.3.1: + resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==} + dependencies: + '@types/react': 18.0.28 + hoist-non-react-statics: 3.3.2 + dev: false + /@types/js-yaml/4.0.5: resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==} dev: true @@ -6151,6 +6164,11 @@ packages: dependencies: browserslist: 4.21.4 + /core-js/3.28.0: + resolution: {integrity: sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw==} + requiresBuild: true + dev: false + /cosmiconfig/6.0.0: resolution: {integrity: sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==} engines: {node: '>=8'} @@ -8125,6 +8143,12 @@ packages: react-is: 16.13.1 dev: false + /html-parse-stringify/3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + dependencies: + void-elements: 3.1.0 + dev: false + /html-void-elements/2.0.1: resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} dev: false @@ -8161,6 +8185,16 @@ packages: hasBin: true dev: true + /i18next-fs-backend/2.1.1: + resolution: {integrity: sha512-FTnj+UmNgT3YRml5ruRv0jMZDG7odOL/OP5PF5mOqvXud2vHrPOOs68Zdk6iqzL47cnnM0ZVkK2BAvpFeDJToA==} + dev: false + + /i18next/22.4.10: + resolution: {integrity: sha512-3EqgGK6fAJRjnGgfkNSStl4mYLCjUoJID338yVyLMj5APT67HUtWoqSayZewiiC5elzMUB1VEUwcmSCoeQcNEA==} + dependencies: + '@babel/runtime': 7.20.13 + dev: false + /idb-keyval/6.2.0: resolution: {integrity: sha512-uw+MIyQn2jl3+hroD7hF8J7PUviBU7BPKWw4f/ISf32D4LoGu98yHjrzWWJDASu9QNrX10tCJqk9YY0ClWm8Ng==} dependencies: @@ -9792,6 +9826,26 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true + /next-i18next/13.1.5_ict23jwgb5pzpgamw2nvwotxa4: + resolution: {integrity: sha512-gULKQNQmFYIW6C45cb5ZOHa+sw8XUkibFHTqRqrcfbAa1fcBCh3TgX1KtzA0vzeh9R93BkkvFhm+FuhO3kR9jQ==} + engines: {node: '>=14'} + peerDependencies: + i18next: ^22.0.6 + next: '>= 12.0.0' + react: '>= 17.0.2' + react-i18next: ^12.1.5 + dependencies: + '@babel/runtime': 7.20.13 + '@types/hoist-non-react-statics': 3.3.1 + core-js: 3.28.0 + hoist-non-react-statics: 3.3.2 + i18next: 22.4.10 + i18next-fs-backend: 2.1.1 + next: 13.1.6_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-i18next: 12.1.5_3yopsigl4h4eb2nqrqfsy65uwi + dev: false + /next-pwa/5.6.0_esbuild@0.17.8+next@13.1.6: resolution: {integrity: sha512-XV8g8C6B7UmViXU8askMEYhWwQ4qc/XqJGnexbLV68hzKaGHZDMtHsm2TNxFcbR7+ypVuth/wwpiIlMwpRJJ5A==} peerDependencies: @@ -10758,6 +10812,26 @@ packages: - csstype dev: false + /react-i18next/12.1.5_3yopsigl4h4eb2nqrqfsy65uwi: + resolution: {integrity: sha512-7PQAv6DA0TcStG96fle+8RfTwxVbHVlZZJPoEszwUNvDuWpGldJmNWa3ZPesEsZQZGF6GkzwvEh6p57qpFD2gQ==} + peerDependencies: + i18next: '>= 19.0.0' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + dependencies: + '@babel/runtime': 7.20.13 + html-parse-stringify: 3.0.1 + i18next: 22.4.10 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /react-infinite-scroller/1.2.6_react@18.2.0: resolution: {integrity: sha512-mGdMyOD00YArJ1S1F3TVU9y4fGSfVVl6p5gh/Vt4u99CJOptfVu/q5V/Wlle72TMgYlBwIhbxK5wF0C/R33PXQ==} peerDependencies: @@ -12832,6 +12906,11 @@ packages: - terser dev: true + /void-elements/3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + dev: false + /w3c-keyname/2.2.6: resolution: {integrity: sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==} dev: false diff --git a/public/locales/en/common.json b/public/locales/en/common.json new file mode 100644 index 00000000..0222f651 --- /dev/null +++ b/public/locales/en/common.json @@ -0,0 +1,3 @@ +{ + "easy": "Easy" +} \ No newline at end of file diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json new file mode 100644 index 00000000..0bddd792 --- /dev/null +++ b/public/locales/zh/common.json @@ -0,0 +1,3 @@ +{ + "easy": "简单" +} \ No newline at end of file diff --git a/src/lib/language-detector.ts b/src/lib/language-detector.ts new file mode 100644 index 00000000..90080832 --- /dev/null +++ b/src/lib/language-detector.ts @@ -0,0 +1,5 @@ +import type { GetServerSidePropsContext } from "next" + +export const languageDetector = (ctx: GetServerSidePropsContext) => { + return ctx.req.headers["accept-language"]?.split(",")[0].split("-")[0] || "en" +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 1f44e21c..a8f285b4 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -11,6 +11,7 @@ import { NotificationModal } from "@crossbell/notification" import { InitContractProvider } from "@crossbell/contract" import NextNProgress from "nextjs-progressbar" import { Network } from "crossbell.js" +import { appWithTranslation } from "next-i18next" import { IPFS_GATEWAY } from "~/lib/env" import { toGateway } from "~/lib/ipfs-parser" @@ -78,4 +79,4 @@ function MyApp({ Component, pageProps }: any) { // return { ...appProps } // } -export default MyApp +export default appWithTranslation(MyApp) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index d659c06a..4d2350f0 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -33,6 +33,9 @@ import { XShopLogo, CrossbellChainLogo, } from "@crossbell/ui" +import { useTranslation } from "next-i18next" +import { serverSideTranslations } from "next-i18next/serverSideTranslations" +import { languageDetector } from "~/lib/language-detector" export const getServerSideProps: GetServerSideProps = async (ctx) => { const queryClient = new QueryClient() @@ -40,6 +43,7 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => { return { props: { + ...(await serverSideTranslations(languageDetector(ctx), ["common"])), dehydratedState: dehydrate(queryClient), }, } @@ -49,6 +53,7 @@ export default function Home() { const isConnected = useAccountState((s) => !!s.computed.account) const router = useRouter() const showcaseSites = useGetSites(showcase) + const { t } = useTranslation("common") const tryNow = useConnectedAction(() => { router.push("/dashboard") @@ -60,7 +65,7 @@ export default function Home() { src: "/assets/easy.png", }, icon: , - title: "Easy", + title: t("easy"), text: ( <> Connect with your Web3 Wallet or{" "}