mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
optimizing i18n and language setting
This commit is contained in:
@@ -11,6 +11,7 @@ importOrder:
|
|||||||
- ^types$
|
- ^types$
|
||||||
- ^@/assets(.*)$
|
- ^@/assets(.*)$
|
||||||
- ^@/api/(.*)$
|
- ^@/api/(.*)$
|
||||||
|
- ^@/i18n/(.*)$
|
||||||
- ^@/types$
|
- ^@/types$
|
||||||
- ^@/lib/(.*)$
|
- ^@/lib/(.*)$
|
||||||
- ^@/jotai/(.*)$
|
- ^@/jotai/(.*)$
|
||||||
|
|||||||
4
web/src/i18n/README.md
Normal file
4
web/src/i18n/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# How to add a language
|
||||||
|
|
||||||
|
1. Add a language file in i18n/locales folder (for example: en.ts).
|
||||||
|
2. Add language key and name in i18n/languages.ts (for example: { key: 'en', name: 'English' }).
|
||||||
@@ -1,14 +1,23 @@
|
|||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
|
import type { Resource } from 'i18next';
|
||||||
import { initReactI18next } from 'react-i18next';
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
|
||||||
import { getLanguage } from '@/lib/localstorage.ts';
|
import { getLanguage } from '@/lib/localstorage.ts';
|
||||||
|
|
||||||
import { en } from './en';
|
function getResources(): Resource {
|
||||||
import { fr } from './fr';
|
const resources: Resource = {};
|
||||||
import { zh } from './zh';
|
|
||||||
import { ru } from './ru';
|
|
||||||
|
|
||||||
const resources = { en, fr, ru, zh };
|
const modules: Record<string, Resource> = import.meta.glob('./locales/*.ts', { eager: true });
|
||||||
|
|
||||||
|
for (const path in modules) {
|
||||||
|
const moduleName = path.split('/').pop()?.replace('.ts', '');
|
||||||
|
if (moduleName) {
|
||||||
|
resources[moduleName] = modules[path].default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources;
|
||||||
|
}
|
||||||
|
|
||||||
function getCurrentLanguage(): string {
|
function getCurrentLanguage(): string {
|
||||||
const languages = Object.keys(resources);
|
const languages = Object.keys(resources);
|
||||||
@@ -26,11 +35,14 @@ function getCurrentLanguage(): string {
|
|||||||
return 'en';
|
return 'en';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resources = getResources();
|
||||||
|
const lng = getCurrentLanguage();
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
resources: resources,
|
resources,
|
||||||
lng: getCurrentLanguage(),
|
lng,
|
||||||
fallbackLng: 'en',
|
fallbackLng: 'en',
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false
|
escapeValue: false
|
||||||
|
|||||||
6
web/src/i18n/languages.ts
Normal file
6
web/src/i18n/languages.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export const languages = [
|
||||||
|
{ key: 'en', name: 'English' },
|
||||||
|
{ key: 'fr', name: 'Français' },
|
||||||
|
{ key: 'ru', name: 'Русский' },
|
||||||
|
{ key: 'zh', name: '中文' }
|
||||||
|
];
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export const en = {
|
const en = {
|
||||||
translation: {
|
translation: {
|
||||||
language: 'Language',
|
language: 'Language',
|
||||||
changePassword: 'Change Password',
|
changePassword: 'Change Password',
|
||||||
@@ -159,3 +159,5 @@ export const en = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default en;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export const fr = {
|
const fr = {
|
||||||
translation: {
|
translation: {
|
||||||
language: 'Langue',
|
language: 'Langue',
|
||||||
changePassword: 'Changer le mot de passe',
|
changePassword: 'Changer le mot de passe',
|
||||||
@@ -163,3 +163,5 @@ export const fr = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default fr;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export const ru = {
|
const ru = {
|
||||||
translation: {
|
translation: {
|
||||||
language: 'Язык',
|
language: 'Язык',
|
||||||
changePassword: 'Изменить пароль',
|
changePassword: 'Изменить пароль',
|
||||||
@@ -17,7 +17,8 @@ export const ru = {
|
|||||||
hddLed: 'Индикатор активности хранилища',
|
hddLed: 'Индикатор активности хранилища',
|
||||||
checkLibFailed: 'Не удалось проверить библиотеку среды выполнения, попробуйте еще раз',
|
checkLibFailed: 'Не удалось проверить библиотеку среды выполнения, попробуйте еще раз',
|
||||||
updateLibFailed: 'Не удалось обновить библиотеку среды выполнения, попробуйте еще раз',
|
updateLibFailed: 'Не удалось обновить библиотеку среды выполнения, попробуйте еще раз',
|
||||||
updatingLib: 'Обновление библиотеки среды выполнения. Пожалуйста, обновите страницу после обновления.',
|
updatingLib:
|
||||||
|
'Обновление библиотеки среды выполнения. Пожалуйста, обновите страницу после обновления.',
|
||||||
checkForUpdate: 'Проверить обновления',
|
checkForUpdate: 'Проверить обновления',
|
||||||
head: {
|
head: {
|
||||||
desktop: 'Удаленный рабочий стол',
|
desktop: 'Удаленный рабочий стол',
|
||||||
@@ -32,7 +33,8 @@ export const ru = {
|
|||||||
placeholderPassword2: 'Введите пароль снова',
|
placeholderPassword2: 'Введите пароль снова',
|
||||||
noEmptyUsername: 'Имя пользователя не может быть пустым',
|
noEmptyUsername: 'Имя пользователя не может быть пустым',
|
||||||
noEmptyPassword: 'Пароль не может быть пустым',
|
noEmptyPassword: 'Пароль не может быть пустым',
|
||||||
noAccount: 'Не удалось получить информацию о пользователе, пожалуйста, обновите веб-страницу или сбросьте пароль',
|
noAccount:
|
||||||
|
'Не удалось получить информацию о пользователе, пожалуйста, обновите веб-страницу или сбросьте пароль',
|
||||||
invalidUser: 'Неверное имя пользователя или пароль',
|
invalidUser: 'Неверное имя пользователя или пароль',
|
||||||
error: 'Непредвиденная ошибка',
|
error: 'Непредвиденная ошибка',
|
||||||
changePassword: 'Изменить пароль',
|
changePassword: 'Изменить пароль',
|
||||||
@@ -55,7 +57,7 @@ export const ru = {
|
|||||||
quality: 'Качество',
|
quality: 'Качество',
|
||||||
frameDetect: 'Экономия трафика',
|
frameDetect: 'Экономия трафика',
|
||||||
frameDetectTip:
|
frameDetectTip:
|
||||||
"Вычисляет разницу между кадрами и прекращает передачу видеопотока, если на экране удаленного узла не обнаружено никаких изменений."
|
'Вычисляет разницу между кадрами и прекращает передачу видеопотока, если на экране удаленного узла не обнаружено никаких изменений.'
|
||||||
},
|
},
|
||||||
cursor: {
|
cursor: {
|
||||||
default: 'Курсор по умолчанию',
|
default: 'Курсор по умолчанию',
|
||||||
@@ -88,7 +90,7 @@ export const ru = {
|
|||||||
tf3: 'Вставьте карту памяти в картридер и подключите его в свой компьютер.',
|
tf3: 'Вставьте карту памяти в картридер и подключите его в свой компьютер.',
|
||||||
tf4: 'Скопируйте файл образа в директорию /data на карте памяти.',
|
tf4: 'Скопируйте файл образа в директорию /data на карте памяти.',
|
||||||
tf5: 'Вставьте карту памяти в NanoKVM.'
|
tf5: 'Вставьте карту памяти в NanoKVM.'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
script: {
|
script: {
|
||||||
title: 'Скрипты',
|
title: 'Скрипты',
|
||||||
@@ -162,3 +164,5 @@ export const ru = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default ru;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export const zh = {
|
const zh = {
|
||||||
translation: {
|
translation: {
|
||||||
currentVersion: '版本',
|
currentVersion: '版本',
|
||||||
latestVersion: '最新版本:',
|
latestVersion: '最新版本:',
|
||||||
@@ -158,3 +158,5 @@ export const zh = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default zh;
|
||||||
@@ -3,6 +3,7 @@ import clsx from 'clsx';
|
|||||||
import { LanguagesIcon } from 'lucide-react';
|
import { LanguagesIcon } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { languages } from '@/i18n/languages.ts';
|
||||||
import { setLanguage } from '@/lib/localstorage.ts';
|
import { setLanguage } from '@/lib/localstorage.ts';
|
||||||
|
|
||||||
export const Language = () => {
|
export const Language = () => {
|
||||||
@@ -15,25 +16,18 @@ export const Language = () => {
|
|||||||
setLanguage(lng);
|
setLanguage(lng);
|
||||||
}
|
}
|
||||||
|
|
||||||
const languageItems = [
|
|
||||||
{ lng: 'en', name: 'English' },
|
|
||||||
{ lng: 'fr', name: 'Français' },
|
|
||||||
{ lng: 'ru', name: 'Русский' },
|
|
||||||
{ lng: 'zh', name: '中文' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
{languageItems.map((item) => (
|
{languages.map((lng) => (
|
||||||
<div
|
<div
|
||||||
key={item.lng}
|
key={lng.key}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'flex cursor-pointer select-none items-center space-x-1 rounded px-5 py-1',
|
'flex cursor-pointer select-none items-center space-x-1 rounded px-5 py-1',
|
||||||
i18n.language === item.lng ? 'text-blue-500' : 'text-white hover:bg-neutral-700'
|
i18n.language === lng.key ? 'text-blue-500' : 'text-white hover:bg-neutral-700'
|
||||||
)}
|
)}
|
||||||
onClick={() => changeLanguage(item.lng)}
|
onClick={() => changeLanguage(lng.key)}
|
||||||
>
|
>
|
||||||
{item.name}
|
{lng.name}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import clsx from 'clsx';
|
|||||||
import { LanguagesIcon } from 'lucide-react';
|
import { LanguagesIcon } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { languages } from '@/i18n/languages.ts';
|
||||||
import { setLanguage } from '@/lib/localstorage.ts';
|
import { setLanguage } from '@/lib/localstorage.ts';
|
||||||
|
|
||||||
export const Language = () => {
|
export const Language = () => {
|
||||||
@@ -15,25 +16,18 @@ export const Language = () => {
|
|||||||
setLanguage(lng);
|
setLanguage(lng);
|
||||||
}
|
}
|
||||||
|
|
||||||
const languageItems = [
|
|
||||||
{ lng: 'en', name: 'English' },
|
|
||||||
{ lng: 'fr', name: 'Français' },
|
|
||||||
{ lng: 'ru', name: 'Русский' },
|
|
||||||
{ lng: 'zh', name: '中文' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
{languageItems.map((item) => (
|
{languages.map((lng) => (
|
||||||
<div
|
<div
|
||||||
key={item.lng}
|
key={lng.key}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'flex cursor-pointer select-none items-center space-x-1 rounded px-5 py-1',
|
'flex cursor-pointer select-none items-center space-x-1 rounded px-5 py-1',
|
||||||
i18n.language === item.lng ? 'text-blue-500' : 'text-white hover:bg-neutral-700'
|
i18n.language === lng.key ? 'text-blue-500' : 'text-white hover:bg-neutral-700'
|
||||||
)}
|
)}
|
||||||
onClick={() => changeLanguage(item.lng)}
|
onClick={() => changeLanguage(lng.key)}
|
||||||
>
|
>
|
||||||
{item.name}
|
{lng.name}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user