feature: i18n support

This commit is contained in:
alikia2x (寒寒) 2024-06-25 02:10:37 +08:00
parent 844333e623
commit 65d4e76fa7
16 changed files with 233 additions and 95 deletions

View File

@ -5,9 +5,11 @@ import { queryAtom } from "lib/state/query";
import { selectedSuggestionAtom } from "lib/state/suggestionSelection";
import handleEnter from "lib/onesearch/handleEnter";
import { suggestionAtom } from "lib/state/suggestion";
import { useTranslation } from "react-i18next";
export default function Search(props: { onFocus: () => void }) {
const { t } = useTranslation();
const settings = useAtomValue(settingsAtom);
const [query, setQuery] = useAtom(queryAtom);
const [selectedSuggestion, setSelected] = useAtom(selectedSuggestionAtom);
@ -43,7 +45,7 @@ export default function Search(props: { onFocus: () => void }) {
dark:placeholder:text-slate-400 text-slate-900 dark:text-white"
id="searchBox"
type="text"
placeholder="placeholder"
placeholder={t('search.placeholder')}
onFocus={props.onFocus}
onKeyDown={handleKeydown}
onChange={(e) =>

View File

@ -38,7 +38,9 @@ export default function Time(props: {
{formatTime()}{" "}
<span className="text-lg leading-9 relative">
{new Intl.DateTimeFormat(navigator.language, {
dateStyle: "medium"
year: "numeric",
month: "short",
day: "numeric"
}).format(currentTime)}
</span>
</div>

5
i18n/ar.json Executable file
View File

@ -0,0 +1,5 @@
{
"search" : {
"placeholder" : "ابحث أو اكتب عنوان URL"
}
}

5
i18n/de.json Executable file
View File

@ -0,0 +1,5 @@
{
"search" : {
"placeholder" : "Suche oder gib eine URL ein"
}
}

5
i18n/en.json Executable file
View File

@ -0,0 +1,5 @@
{
"search" : {
"placeholder" : "Search or type a URL"
}
}

5
i18n/es.json Executable file
View File

@ -0,0 +1,5 @@
{
"search" : {
"placeholder" : "Buscar o escribir una URL"
}
}

5
i18n/fr.json Executable file
View File

@ -0,0 +1,5 @@
{
"search" : {
"placeholder" : "Rechercher ou saisir une URL"
}
}

5
i18n/it.json Executable file
View File

@ -0,0 +1,5 @@
{
"search" : {
"placeholder" : "Cerca o digita un URL"
}
}

5
i18n/ja.json Executable file
View File

@ -0,0 +1,5 @@
{
"search" : {
"placeholder" : "検索またはURLを入力"
}
}

5
i18n/ko.json Executable file
View File

@ -0,0 +1,5 @@
{
"search" : {
"placeholder" : "검색 또는 URL 입력"
}
}

5
i18n/pt.json Executable file
View File

@ -0,0 +1,5 @@
{
"search" : {
"placeholder" : "Pesquisar ou digitar uma URL"
}
}

5
i18n/ru.json Executable file
View File

@ -0,0 +1,5 @@
{
"search" : {
"placeholder" : "Искать или ввести URL"
}
}

5
i18n/zh.json Executable file
View File

@ -0,0 +1,5 @@
{
"search" : {
"placeholder" : "搜索或输入网址"
}
}

View File

@ -1,7 +1,7 @@
{
"name": "sparkhome",
"private": false,
"version": "5.0.0",
"version": "5.1.0",
"type": "module",
"scripts": {
"dev": "vite",
@ -10,9 +10,13 @@
"preview": "vite preview"
},
"dependencies": {
"i18next": "^23.11.5",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-icu": "^2.3.0",
"jotai": "^2.8.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^14.1.2",
"react-router": "^6.23.1",
"react-router-dom": "^6.23.1",
"search-engine-autocomplete": "^0.4.3",

View File

@ -8,6 +8,15 @@ importers:
.:
dependencies:
i18next:
specifier: ^23.11.5
version: 23.11.5
i18next-browser-languagedetector:
specifier: ^8.0.0
version: 8.0.0
i18next-icu:
specifier: ^2.3.0
version: 2.3.0(intl-messageformat@10.5.14)
jotai:
specifier: ^2.8.3
version: 2.8.3(@types/react@18.3.3)(react@18.3.1)
@ -17,9 +26,9 @@ importers:
react-dom:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
react-intl:
specifier: ^6.6.8
version: 6.6.8(react@18.3.1)(typescript@5.5.2)
react-i18next:
specifier: ^14.1.2
version: 14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-router:
specifier: ^6.23.1
version: 6.23.1(react@18.3.1)
@ -53,7 +62,7 @@ importers:
version: 7.13.1(eslint@8.57.0)(typescript@5.5.2)
'@vitejs/plugin-react-swc':
specifier: ^3.5.0
version: 3.7.0(vite@5.3.1)
version: 3.7.0(vite@5.3.1(@types/node@20.14.8))
autoprefixer:
specifier: ^10.4.19
version: 10.4.19(postcss@8.4.38)
@ -77,13 +86,13 @@ importers:
version: 5.5.2
vite:
specifier: ^5.3.1
version: 5.3.1
version: 5.3.1(@types/node@20.14.8)
vite-plugin-pages:
specifier: ^0.32.2
version: 0.32.2(react-router@6.23.1(react@18.3.1))(vite@5.3.1)
version: 0.32.2(react-router@6.23.1(react@18.3.1))(vite@5.3.1(@types/node@20.14.8))
vite-tsconfig-paths:
specifier: ^4.3.2
version: 4.3.2(typescript@5.5.2)(vite@5.3.1)
version: 4.3.2(typescript@5.5.2)(vite@5.3.1(@types/node@20.14.8))
packages:
@ -91,6 +100,10 @@ packages:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
'@babel/runtime@7.24.7':
resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==}
engines: {node: '>=6.9.0'}
'@esbuild/aix-ppc64@0.21.5':
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
engines: {node: '>=12'}
@ -259,23 +272,9 @@ packages:
'@formatjs/icu-skeleton-parser@1.8.2':
resolution: {integrity: sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==}
'@formatjs/intl-displaynames@6.6.8':
resolution: {integrity: sha512-Lgx6n5KxN16B3Pb05z3NLEBQkGoXnGjkTBNCZI+Cn17YjHJ3fhCeEJJUqRlIZmJdmaXQhjcQVDp6WIiNeRYT5g==}
'@formatjs/intl-listformat@7.5.7':
resolution: {integrity: sha512-MG2TSChQJQT9f7Rlv+eXwUFiG24mKSzmF144PLb8m8OixyXqn4+YWU+5wZracZGCgVTVmx8viCf7IH3QXoiB2g==}
'@formatjs/intl-localematcher@0.5.4':
resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==}
'@formatjs/intl@2.10.4':
resolution: {integrity: sha512-56483O+HVcL0c7VucAS2tyH020mt9XTozZO67cwtGg0a7KWDukS/FzW3OnvaHmTHDuYsoPIzO+ZHVfU6fT/bJw==}
peerDependencies:
typescript: ^4.7 || 5
peerDependenciesMeta:
typescript:
optional: true
'@humanwhocodes/config-array@0.11.14':
resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
engines: {node: '>=10.10.0'}
@ -492,12 +491,12 @@ packages:
'@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
'@types/hoist-non-react-statics@3.3.5':
resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==}
'@types/ms@0.7.34':
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
'@types/node@20.14.8':
resolution: {integrity: sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==}
'@types/prop-types@15.7.12':
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
@ -965,8 +964,19 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
hoist-non-react-statics@3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
html-parse-stringify@3.0.1:
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
i18next-browser-languagedetector@8.0.0:
resolution: {integrity: sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==}
i18next-icu@2.3.0:
resolution: {integrity: sha512-x+j7kd5nDJCfbU53uwsMfXD7ALPu5uv0bqjAMQ5nVvXRoj1L7gkmswKtM3XDWYo4YUHf1jznlhSdPyy0xEwU+Q==}
peerDependencies:
intl-messageformat: ^10.3.3
i18next@23.11.5:
resolution: {integrity: sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==}
ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
@ -1367,17 +1377,18 @@ packages:
peerDependencies:
react: ^18.3.1
react-intl@6.6.8:
resolution: {integrity: sha512-M0pkhzcgV31h++2901BiRXWl69hp2zPyLxRrSwRjd1ErXbNoubz/f4M6DrRTd4OiSUrT4ajRQzrmtS5plG4FtA==}
react-i18next@14.1.2:
resolution: {integrity: sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==}
peerDependencies:
react: ^16.6.0 || 17 || 18
typescript: ^4.7 || 5
i18next: '>= 23.2.3'
react: '>= 16.8.0'
react-dom: '*'
react-native: '*'
peerDependenciesMeta:
typescript:
react-dom:
optional: true
react-native:
optional: true
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
react-router-dom@6.23.1:
resolution: {integrity: sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==}
@ -1403,6 +1414,9 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
regexp.prototype.flags@1.5.2:
resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==}
engines: {node: '>= 0.4'}
@ -1569,6 +1583,9 @@ packages:
ufo@1.5.3:
resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==}
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
update-browserslist-db@1.0.16:
resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==}
hasBin: true
@ -1641,6 +1658,10 @@ packages:
terser:
optional: true
void-elements@3.1.0:
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
engines: {node: '>=0.10.0'}
which-boxed-primitive@1.0.2:
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
@ -1685,6 +1706,10 @@ snapshots:
'@alloc/quick-lru@5.2.0': {}
'@babel/runtime@7.24.7':
dependencies:
regenerator-runtime: 0.14.1
'@esbuild/aix-ppc64@0.21.5':
optional: true
@ -1797,34 +1822,10 @@ snapshots:
'@formatjs/ecma402-abstract': 2.0.0
tslib: 2.6.3
'@formatjs/intl-displaynames@6.6.8':
dependencies:
'@formatjs/ecma402-abstract': 2.0.0
'@formatjs/intl-localematcher': 0.5.4
tslib: 2.6.3
'@formatjs/intl-listformat@7.5.7':
dependencies:
'@formatjs/ecma402-abstract': 2.0.0
'@formatjs/intl-localematcher': 0.5.4
tslib: 2.6.3
'@formatjs/intl-localematcher@0.5.4':
dependencies:
tslib: 2.6.3
'@formatjs/intl@2.10.4(typescript@5.5.2)':
dependencies:
'@formatjs/ecma402-abstract': 2.0.0
'@formatjs/fast-memoize': 2.2.0
'@formatjs/icu-messageformat-parser': 2.7.8
'@formatjs/intl-displaynames': 6.6.8
'@formatjs/intl-listformat': 7.5.7
intl-messageformat: 10.5.14
tslib: 2.6.3
optionalDependencies:
typescript: 5.5.2
'@humanwhocodes/config-array@0.11.14':
dependencies:
'@humanwhocodes/object-schema': 2.0.3
@ -1986,13 +1987,13 @@ snapshots:
'@types/estree@1.0.5': {}
'@types/hoist-non-react-statics@3.3.5':
dependencies:
'@types/react': 18.3.3
hoist-non-react-statics: 3.3.2
'@types/ms@0.7.34': {}
'@types/node@20.14.8':
dependencies:
undici-types: 5.26.5
optional: true
'@types/prop-types@15.7.12': {}
'@types/react-dom@18.3.0':
@ -2089,10 +2090,10 @@ snapshots:
'@ungap/structured-clone@1.2.0': {}
'@vitejs/plugin-react-swc@3.7.0(vite@5.3.1)':
'@vitejs/plugin-react-swc@3.7.0(vite@5.3.1(@types/node@20.14.8))':
dependencies:
'@swc/core': 1.6.5
vite: 5.3.1
vite: 5.3.1(@types/node@20.14.8)
transitivePeerDependencies:
- '@swc/helpers'
@ -2556,9 +2557,21 @@ snapshots:
dependencies:
function-bind: 1.1.2
hoist-non-react-statics@3.3.2:
html-parse-stringify@3.0.1:
dependencies:
react-is: 16.13.1
void-elements: 3.1.0
i18next-browser-languagedetector@8.0.0:
dependencies:
'@babel/runtime': 7.24.7
i18next-icu@2.3.0(intl-messageformat@10.5.14):
dependencies:
intl-messageformat: 10.5.14
i18next@23.11.5:
dependencies:
'@babel/runtime': 7.24.7
ignore@5.3.1: {}
@ -2902,23 +2915,14 @@ snapshots:
react: 18.3.1
scheduler: 0.23.2
react-intl@6.6.8(react@18.3.1)(typescript@5.5.2):
react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@formatjs/ecma402-abstract': 2.0.0
'@formatjs/icu-messageformat-parser': 2.7.8
'@formatjs/intl': 2.10.4(typescript@5.5.2)
'@formatjs/intl-displaynames': 6.6.8
'@formatjs/intl-listformat': 7.5.7
'@types/hoist-non-react-statics': 3.3.5
'@types/react': 18.3.3
hoist-non-react-statics: 3.3.2
intl-messageformat: 10.5.14
'@babel/runtime': 7.24.7
html-parse-stringify: 3.0.1
i18next: 23.11.5
react: 18.3.1
tslib: 2.6.3
optionalDependencies:
typescript: 5.5.2
react-is@16.13.1: {}
react-dom: 18.3.1(react@18.3.1)
react-router-dom@6.23.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
@ -2944,6 +2948,8 @@ snapshots:
dependencies:
picomatch: 2.3.1
regenerator-runtime@0.14.1: {}
regexp.prototype.flags@1.5.2:
dependencies:
call-bind: 1.0.7
@ -3139,6 +3145,9 @@ snapshots:
ufo@1.5.3: {}
undici-types@5.26.5:
optional: true
update-browserslist-db@1.0.16(browserslist@4.23.1):
dependencies:
browserslist: 4.23.1
@ -3155,7 +3164,7 @@ snapshots:
validate-color@2.2.4: {}
vite-plugin-pages@0.32.2(react-router@6.23.1(react@18.3.1))(vite@5.3.1):
vite-plugin-pages@0.32.2(react-router@6.23.1(react@18.3.1))(vite@5.3.1(@types/node@20.14.8)):
dependencies:
'@types/debug': 4.1.12
debug: 4.3.5
@ -3165,32 +3174,35 @@ snapshots:
json5: 2.2.3
local-pkg: 0.5.0
picocolors: 1.0.1
vite: 5.3.1
vite: 5.3.1(@types/node@20.14.8)
yaml: 2.4.5
optionalDependencies:
react-router: 6.23.1(react@18.3.1)
transitivePeerDependencies:
- supports-color
vite-tsconfig-paths@4.3.2(typescript@5.5.2)(vite@5.3.1):
vite-tsconfig-paths@4.3.2(typescript@5.5.2)(vite@5.3.1(@types/node@20.14.8)):
dependencies:
debug: 4.3.5
globrex: 0.1.2
tsconfck: 3.1.0(typescript@5.5.2)
optionalDependencies:
vite: 5.3.1
vite: 5.3.1(@types/node@20.14.8)
transitivePeerDependencies:
- supports-color
- typescript
vite@5.3.1:
vite@5.3.1(@types/node@20.14.8):
dependencies:
esbuild: 0.21.5
postcss: 8.4.38
rollup: 4.18.0
optionalDependencies:
'@types/node': 20.14.8
fsevents: 2.3.3
void-elements@3.1.0: {}
which-boxed-primitive@1.0.2:
dependencies:
is-bigint: 1.0.4

View File

@ -1,11 +1,74 @@
import { Suspense } from "react";
import { useRoutes } from "react-router-dom";
import routes from "~react-pages";
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from 'i18next-browser-languagedetector';
import ICU from 'i18next-icu';
import * as en from "i18n/en.json"
import * as zh from "i18n/zh.json"
import * as ja from "i18n/ja.json"
import * as ar from "i18n/ar.json"
import * as de from "i18n/de.json"
import * as es from "i18n/es.json"
import * as fr from "i18n/fr.json"
import * as it from "i18n/it.json"
import * as ko from "i18n/ko.json"
import * as pt from "i18n/pt.json"
import * as ru from "i18n/ru.json"
i18n.use(initReactI18next) // passes i18n down to react-i18next
.use(LanguageDetector)
.use(ICU)
.init({
resources: {
en: {
translation: en
},
zh: {
translation: zh
},
ja: {
translation: ja
},
ar: {
translation: ar
},
de: {
translation: de
},
es: {
translation: es
},
fr: {
translation: fr
},
it: {
translation: it
},
ko: {
translation: ko
},
pt: {
translation: pt
},
ru: {
translation: ru
}
},
fallbackLng: "en",
interpolation: {
escapeValue: false // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
},
detection: {
order: ['navigator'],
caches: []
}
});
export function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
{useRoutes(routes)}
</Suspense>
)
}
return <Suspense fallback={<p>Loading...</p>}>{useRoutes(routes)}</Suspense>;
}