Skip to content

Commit 9eab58f

Browse files
committed
perf: split translations at build time
1 parent 95368c6 commit 9eab58f

File tree

4 files changed

+80
-40
lines changed

4 files changed

+80
-40
lines changed

package.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
"private": true,
44
"version": "0.0.0",
55
"scripts": {
6-
"dev": "vite",
7-
"build": "tsc && vite build",
8-
"build:skiptsc": "vite build",
9-
"build:azure": "yarn build:skiptsc && node post-build.mjs",
10-
"preview": "vite preview",
6+
"dev": "node scripts/split-translations.js && vite",
7+
"build": "node scripts/split-translations.js && tsc && vite build",
8+
"build:skiptsc": "node scripts/split-translations.js && vite build",
9+
"build:azure": "node scripts/split-translations.js && yarn build:skiptsc && node post-build.mjs",
10+
"preview": "node scripts/split-translations.js && vite preview",
1111
"lint": "npm-run-all --parallel lint:check:eslint lint:check:prettier",
1212
"lint:check:eslint": "eslint src/**/*",
1313
"lint:check:prettier": "prettier --check src/**/*",
@@ -17,7 +17,8 @@
1717
"scripts:update": "yarn install && npm-run-all --parallel scripts:update-operators scripts:update-operator-avatars scripts:update-prof-icons",
1818
"scripts:update-operators": "esno scripts/update-operators.ts",
1919
"scripts:update-operator-avatars": "esno scripts/update-operator-avatars.ts",
20-
"scripts:update-prof-icons": "yarn install && esno scripts/update-prof-icons.ts"
20+
"scripts:update-prof-icons": "yarn install && esno scripts/update-prof-icons.ts",
21+
"scripts:split-translations": "node scripts/split-translations.js"
2122
},
2223
"dependencies": {
2324
"@blueprintjs/core": "^4.15.1",
@@ -42,6 +43,7 @@
4243
"fuse.js": "^6.6.2",
4344
"i18next": "^25.0.0",
4445
"i18next-browser-languagedetector": "^8.0.4",
46+
"i18next-http-backend": "^3.0.2",
4547
"jotai": "^2.7.0",
4648
"linkify-react": "^3.0.4",
4749
"linkifyjs": "^3.0.5",

scripts/split-translations.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
const fs = require('fs')
2+
const path = require('path')
3+
4+
const translations = require('../src/i18n/translations.json')
5+
6+
const outputDir = path.join(__dirname, '../public/locales')
7+
if (!fs.existsSync(outputDir)) {
8+
fs.mkdirSync(outputDir, { recursive: true })
9+
}
10+
11+
const languages = ['en', 'cn']
12+
13+
function extractLanguageData(lang) {
14+
const result = {}
15+
16+
function extractFromObject(obj, currentPath = []) {
17+
for (const [key, value] of Object.entries(obj)) {
18+
const newPath = [...currentPath, key]
19+
20+
if (
21+
value &&
22+
typeof value === 'object' &&
23+
'cn' in value &&
24+
'en' in value
25+
) {
26+
setNestedValue(result, newPath, value[lang])
27+
} else if (value && typeof value === 'object') {
28+
extractFromObject(value, newPath)
29+
}
30+
}
31+
}
32+
33+
function setNestedValue(obj, path, value) {
34+
let current = obj
35+
for (let i = 0; i < path.length - 1; i++) {
36+
if (!current[path[i]]) current[path[i]] = {}
37+
current = current[path[i]]
38+
}
39+
current[path[path.length - 1]] = value
40+
}
41+
42+
extractFromObject(translations)
43+
return result
44+
}
45+
46+
languages.forEach((lang) => {
47+
const data = extractLanguageData(lang)
48+
fs.writeFileSync(
49+
path.join(outputDir, `${lang}.json`),
50+
JSON.stringify(data, null, 2),
51+
)
52+
console.log(`Generated ${lang}.json`)
53+
})

src/i18n/index.ts

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,8 @@
11
import i18n from 'i18next'
22
import LanguageDetector from 'i18next-browser-languagedetector'
3+
import Backend from 'i18next-http-backend'
34
import { initReactI18next } from 'react-i18next'
45

5-
import translations from './translations.json'
6-
7-
const flattenTranslations = (obj, lang) => {
8-
const result = {}
9-
10-
const flatten = (current, prefix = '') => {
11-
Object.entries(current).forEach(([key, value]) => {
12-
const newKey = prefix ? `${prefix}.${key}` : key
13-
14-
if (
15-
value &&
16-
typeof value === 'object' &&
17-
'cn' in value &&
18-
'en' in value
19-
) {
20-
result[newKey] = value[lang]
21-
} else if (value && typeof value === 'object') {
22-
flatten(value, newKey)
23-
}
24-
})
25-
}
26-
27-
flatten(obj)
28-
return result
29-
}
30-
316
const languageDetectorOptions = {
327
order: ['localStorage', 'navigator'],
338
lookupNavigator: 'language',
@@ -40,16 +15,12 @@ const languageDetectorOptions = {
4015
}
4116

4217
i18n
18+
.use(Backend)
4319
.use(LanguageDetector)
4420
.use(initReactI18next)
4521
.init({
46-
resources: {
47-
cn: {
48-
translation: flattenTranslations(translations, 'cn'),
49-
},
50-
en: {
51-
translation: flattenTranslations(translations, 'en'),
52-
},
22+
backend: {
23+
loadPath: '/locales/{{lng}}.json',
5324
},
5425
fallbackLng: 'cn',
5526
detection: languageDetectorOptions,

yarn.lock

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2084,6 +2084,13 @@ cosmiconfig@^7.0.0:
20842084
path-type "^4.0.0"
20852085
yaml "^1.10.0"
20862086

2087+
2088+
version "4.0.0"
2089+
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983"
2090+
integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==
2091+
dependencies:
2092+
node-fetch "^2.6.12"
2093+
20872094
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
20882095
version "7.0.3"
20892096
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -3139,6 +3146,13 @@ i18next-browser-languagedetector@^8.0.4:
31393146
dependencies:
31403147
"@babel/runtime" "^7.23.2"
31413148

3149+
i18next-http-backend@^3.0.2:
3150+
version "3.0.2"
3151+
resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz#7c8daa31aa69679e155ec1f96a37846225bdf907"
3152+
integrity sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==
3153+
dependencies:
3154+
cross-fetch "4.0.0"
3155+
31423156
i18next@^25.0.0:
31433157
version "25.0.0"
31443158
resolved "https://registry.yarnpkg.com/i18next/-/i18next-25.0.0.tgz#ae6b44dea48597e556a126c6f4f10d9c18cc62e2"
@@ -4269,7 +4283,7 @@ node-domexception@^1.0.0:
42694283
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
42704284
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
42714285

4272-
node-fetch@^2.6.7:
4286+
node-fetch@^2.6.12, node-fetch@^2.6.7:
42734287
version "2.7.0"
42744288
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
42754289
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==

0 commit comments

Comments
 (0)