- Dashboard
+ {t("global.dashboard")}
diff --git a/src/app/dashboard/vm/create-random-invoice-button-vm.ts b/src/app/[lang]/dashboard/vm/create-random-invoice-button-vm.ts
similarity index 100%
rename from src/app/dashboard/vm/create-random-invoice-button-vm.ts
rename to src/app/[lang]/dashboard/vm/create-random-invoice-button-vm.ts
diff --git a/src/app/page.tsx b/src/app/[lang]/page.tsx
similarity index 100%
rename from src/app/page.tsx
rename to src/app/[lang]/page.tsx
diff --git a/src/app/dashboard/loading.tsx b/src/app/dashboard/loading.tsx
deleted file mode 100644
index c50ad40..0000000
--- a/src/app/dashboard/loading.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import DashboardSkeleton from "@/app/dashboard/components/server/skeletons/skeletons";
-
-export default function Loading() {
- return ;
-}
\ No newline at end of file
diff --git a/src/bootstrap/i18n/dictionaries/en.ts b/src/bootstrap/i18n/dictionaries/en.ts
new file mode 100644
index 0000000..3bd88ce
--- /dev/null
+++ b/src/bootstrap/i18n/dictionaries/en.ts
@@ -0,0 +1,15 @@
+import langKey from "@/bootstrap/i18n/dictionaries/lang-key"
+
+const en: typeof langKey = {
+ global: {
+ home: "Home",
+ dashboard: "Dashboard"
+ },
+ dashboard: {
+ invoice: {
+ createButton: "Create random Invoice"
+ }
+ }
+}
+
+export default en
\ No newline at end of file
diff --git a/src/bootstrap/i18n/dictionaries/lang-key.ts b/src/bootstrap/i18n/dictionaries/lang-key.ts
new file mode 100644
index 0000000..aed439c
--- /dev/null
+++ b/src/bootstrap/i18n/dictionaries/lang-key.ts
@@ -0,0 +1,13 @@
+const langKey = {
+ global: {
+ home: "Дом",
+ dashboard: "Панель приборов"
+ },
+ dashboard: {
+ invoice: {
+ createButton: "Создать случайный счет-фактуру"
+ }
+ }
+}
+
+export default langKey;
\ No newline at end of file
diff --git a/src/bootstrap/i18n/dictionaries/ru.ts b/src/bootstrap/i18n/dictionaries/ru.ts
new file mode 100644
index 0000000..5b6c4b4
--- /dev/null
+++ b/src/bootstrap/i18n/dictionaries/ru.ts
@@ -0,0 +1,15 @@
+import langKey from "@/bootstrap/i18n/dictionaries/lang-key"
+
+const ru: typeof langKey = {
+ global: {
+ home: "Дом",
+ dashboard: "Панель приборов"
+ },
+ dashboard: {
+ invoice: {
+ createButton: "Создать случайный счет-фактуру"
+ }
+ }
+}
+
+export default ru
\ No newline at end of file
diff --git a/src/bootstrap/i18n/i18n.ts b/src/bootstrap/i18n/i18n.ts
new file mode 100644
index 0000000..9cc5fb0
--- /dev/null
+++ b/src/bootstrap/i18n/i18n.ts
@@ -0,0 +1,21 @@
+import { getOptions } from '@/bootstrap/i18n/settings'
+import { createInstance } from 'i18next'
+import resourcesToBackend from 'i18next-resources-to-backend'
+import { initReactI18next } from 'react-i18next/initReactI18next'
+
+const initI18next = async (lng: string, ns?: string) => {
+ const i18nInstance = createInstance()
+ await i18nInstance
+ .use(initReactI18next)
+ .use(resourcesToBackend((language: string) => import(`./dictionaries/${language}.ts`)))
+ .init(getOptions(lng, ns))
+ return i18nInstance
+}
+
+export async function getTranslation(lng: string, ns?: string, options: {keyPrefix?: string} = {}) {
+ const i18nextInstance = await initI18next(lng, ns)
+ return {
+ t: i18nextInstance.getFixedT(lng, Array.isArray(ns) ? ns[0] : ns, options?.keyPrefix),
+ i18n: i18nextInstance
+ }
+}
\ No newline at end of file
diff --git a/src/bootstrap/i18n/settings.ts b/src/bootstrap/i18n/settings.ts
new file mode 100644
index 0000000..90f4fa0
--- /dev/null
+++ b/src/bootstrap/i18n/settings.ts
@@ -0,0 +1,15 @@
+export const fallbackLng = 'en'
+export const languages = [fallbackLng, 'ru']
+export const defaultNS = 'translation'
+
+export function getOptions (lng = fallbackLng, ns = defaultNS) {
+ return {
+ // debug: true,
+ supportedLngs: languages,
+ fallbackLng,
+ lng,
+ fallbackNS: defaultNS,
+ defaultNS,
+ ns
+ }
+}
\ No newline at end of file
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000..ee54225
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,23 @@
+import { fallbackLng, languages } from "@/bootstrap/i18n/settings";
+import { NextRequest, NextResponse } from "next/server";
+
+export function middleware(request: NextRequest) {
+ const { pathname } = request.nextUrl
+ const pathnameHasLocale = languages.some(
+ (lang) => pathname.startsWith(`/${lang}/`) || pathname === `/${lang}`
+ )
+
+ if (pathnameHasLocale) return
+
+ request.nextUrl.pathname = `/${fallbackLng}${pathname}`
+ // e.g. incoming request is /products
+ // The new URL is now /en-US/products
+ return NextResponse.redirect(request.nextUrl)
+}
+
+export const config = {
+ matcher: [
+ // Skip all internal paths (_next)
+ '/((?!api|_next/static|_next/image|favicon.ico).*)'
+ ],
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index 0d0b9fb..9f3a85a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -25,6 +25,6 @@
"@/*": ["./src/*"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/bootstrap/boundaries/db/seed.js", "src/bootstrap/boundaries/db/placeholder-data.js"],
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/bootstrap/boundaries/db/seed.js", "src/bootstrap/boundaries/db/placeholder-data.js", "src/middleware.ts"],
"exclude": ["node_modules"]
}
diff --git a/yarn.lock b/yarn.lock
index 194f43b..4fc4079 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -138,7 +138,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.25.9"
-"@babel/runtime@^7.12.5":
+"@babel/runtime@^7.12.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.25.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1"
integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==
@@ -2342,6 +2342,13 @@ html-encoding-sniffer@^4.0.0:
dependencies:
whatwg-encoding "^3.1.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"
+
http-proxy-agent@^7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e"
@@ -2366,6 +2373,20 @@ https-proxy-agent@^7.0.5:
agent-base "^7.0.2"
debug "4"
+i18next-resources-to-backend@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/i18next-resources-to-backend/-/i18next-resources-to-backend-1.2.1.tgz#fded121e63e3139ce839c9901b9449dbbea7351d"
+ integrity sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==
+ dependencies:
+ "@babel/runtime" "^7.23.2"
+
+i18next@^23.16.4:
+ version "23.16.4"
+ resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.16.4.tgz#79c07544f6d6fa803fe8427108d547542c1d6cf4"
+ integrity sha512-9NIYBVy9cs4wIqzurf7nLXPyf3R78xYbxExVqHLK9od3038rjpyOEzW+XB130kZ1N4PZ9inTtJ471CRJ4Ituyg==
+ dependencies:
+ "@babel/runtime" "^7.23.2"
+
iconv-lite@0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
@@ -3291,6 +3312,14 @@ react-dom@19.0.0-rc-69d4b800-20241021:
dependencies:
scheduler "0.25.0-rc-69d4b800-20241021"
+react-i18next@^15.1.0:
+ version "15.1.0"
+ resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.1.0.tgz#9494e4add2389f04c205dd7628c1aa75747b98a3"
+ integrity sha512-zj3nJynMnZsy2gPZiOTC7XctCY5eQGqT3tcKMmfJWC9FMvgd+960w/adq61j8iPzpwmsXejqID9qC3Mqu1Xu2Q==
+ dependencies:
+ "@babel/runtime" "^7.25.0"
+ html-parse-stringify "^3.0.1"
+
react-is@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -4099,6 +4128,11 @@ vitest@^2.1.4:
vite-node "2.1.4"
why-is-node-running "^2.3.0"
+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==
+
w3c-xmlserializer@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c"