Skip to content

Commit 5c012a3

Browse files
committed
🐛 修复i18n在没有支持语言下无法保存的问题 #485
Fixed the issue that i18n cannot be saved without supporting languages
1 parent e311746 commit 5c012a3

3 files changed

Lines changed: 111 additions & 34 deletions

File tree

src/locales/locales.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,19 @@ import zhTW from "./zh-TW/translation.json";
1010
import achUG from "./ach-UG/translation.json";
1111
import jaJP from "./ja/translation.json";
1212
import deDE from "./de/translation.json";
13+
import "dayjs/locale/en";
14+
import "dayjs/locale/vi";
1315
import "dayjs/locale/zh-cn";
1416
import "dayjs/locale/zh-tw";
1517
import "dayjs/locale/ja";
1618
import "dayjs/locale/de";
1719
import { systemConfig } from "@App/pages/store/global";
1820

21+
const uiLanguage = chrome.i18n.getUILanguage();
22+
1923
i18n.use(initReactI18next).init({
20-
fallbackLng: "en",
21-
lng: chrome.i18n.getUILanguage(),
24+
fallbackLng: "en-US",
25+
lng: globalThis.localStorage ? localStorage["language"] || uiLanguage : uiLanguage, // 优先使用localStorage中的语言设置
2226
interpolation: {
2327
escapeValue: false, // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
2428
},
@@ -35,15 +39,18 @@ i18n.use(initReactI18next).init({
3539

3640
export let localePath = "";
3741

38-
chrome.i18n.getAcceptLanguages((lngs) => {
39-
systemConfig.getLanguage(lngs).then((lng) => {
40-
i18n.changeLanguage(lng);
41-
dayjs.locale(lng.toLocaleLowerCase());
42-
if (lng !== "zh-CN") {
43-
localePath = "en";
44-
}
45-
});
46-
});
42+
async function initLanguage() {
43+
const lng = await systemConfig.getLanguage();
44+
i18n.changeLanguage(lng);
45+
dayjs.locale(lng.toLocaleLowerCase());
46+
if (lng !== "zh-CN") {
47+
localePath = "en";
48+
}
49+
}
50+
51+
setTimeout(() => {
52+
initLanguage();
53+
}, 0);
4754

4855
dayjs.extend(relativeTime);
4956

@@ -59,4 +66,17 @@ export function i18nDescription(script: { metadata: Metadata }) {
5966
: script.metadata.description;
6067
}
6168

69+
// 匹配语言
70+
export async function matchLanguage() {
71+
const acceptLanguages = await chrome.i18n.getAcceptLanguages();
72+
// 遍历数组寻找匹配语言
73+
for (let i = 0; i < acceptLanguages.length; i += 1) {
74+
const lng = acceptLanguages[i];
75+
if (i18n.hasResourceBundle(lng, "translation")) {
76+
return lng;
77+
}
78+
}
79+
return "";
80+
}
81+
6282
export default i18n;

src/pages/components/layout/MainLayout.tsx

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,21 @@ import {
1717
IconCloseCircle,
1818
IconDesktop,
1919
IconDown,
20+
IconLanguage,
2021
IconLink,
2122
IconMoonFill,
2223
IconSunFill,
2324
} from "@arco-design/web-react/icon";
24-
import React, { ReactNode, useRef, useState } from "react";
25+
import React, { ReactNode, useEffect, useRef, useState } from "react";
2526
import { useTranslation } from "react-i18next";
2627
import "./index.css";
2728
import { useAppDispatch, useAppSelector } from "@App/pages/store/hooks";
2829
import { selectThemeMode, setDarkMode } from "@App/pages/store/features/config";
2930
import { RiFileCodeLine, RiImportLine, RiPlayListAddLine, RiTerminalBoxLine, RiTimerLine } from "react-icons/ri";
3031
import { scriptClient } from "@App/pages/store/features/script";
3132
import { useDropzone } from "react-dropzone";
33+
import i18n, { matchLanguage } from "@App/locales/locales";
34+
import { systemConfig } from "@App/pages/store/global";
3235

3336
const readFile = (file: File): Promise<string> => {
3437
return new Promise((resolve) => {
@@ -57,7 +60,6 @@ const uploadFiles = async (files: File[], importByUrlsFunc: (urls: string[]) =>
5760
importByUrlsFunc(urls);
5861
};
5962

60-
6163
const MainLayout: React.FC<{
6264
children: ReactNode;
6365
className: string;
@@ -67,6 +69,7 @@ const MainLayout: React.FC<{
6769
const dispatch = useAppDispatch();
6870
const importRef = useRef<RefTextAreaType>(null);
6971
const [importVisible, setImportVisible] = useState(false);
72+
const [showLanguage, setShowLanguage] = useState(false);
7073
const { t } = useTranslation();
7174

7275
const importByUrlsLocal = async (urls: string[]) => {
@@ -104,6 +107,30 @@ const MainLayout: React.FC<{
104107
},
105108
});
106109

110+
const languageList: { key: string; title: string }[] = [];
111+
Object.keys(i18n.store.data).forEach((key) => {
112+
if (key === "ach-UG") {
113+
return;
114+
}
115+
languageList.push({
116+
key,
117+
title: i18n.store.data[key].title as string,
118+
});
119+
});
120+
languageList.push({
121+
key: "help",
122+
title: t("help_translate"),
123+
});
124+
125+
useEffect(() => {
126+
// 当没有匹配语言且系统配置中没有设置语言时显示语言按钮
127+
matchLanguage().then((result) => {
128+
if (!result) {
129+
setShowLanguage(true);
130+
}
131+
});
132+
});
133+
107134
return (
108135
<ConfigProvider
109136
renderEmpty={() => {
@@ -130,12 +157,7 @@ const MainLayout: React.FC<{
130157
setImportVisible(false);
131158
}}
132159
>
133-
<Input.TextArea
134-
ref={importRef}
135-
rows={8}
136-
placeholder={t("import_script_placeholder")}
137-
defaultValue=""
138-
/>
160+
<Input.TextArea ref={importRef} rows={8} placeholder={t("import_script_placeholder")} defaultValue="" />
139161
</Modal>
140162
<div className="flex row items-center">
141163
<img style={{ height: "40px" }} src="/assets/logo.png" alt="ScriptCat" />
@@ -232,6 +254,40 @@ const MainLayout: React.FC<{
232254
className="!text-lg"
233255
/>
234256
</Dropdown>
257+
{showLanguage && (
258+
<Dropdown
259+
droplist={
260+
<Menu>
261+
{languageList.map((value) => (
262+
<Menu.Item
263+
key={value.key}
264+
onClick={() => {
265+
if (value.key === "help") {
266+
window.open("https://crowdin.com/project/scriptcat", "_blank");
267+
return;
268+
}
269+
systemConfig.setLanguage(value.key);
270+
Message.success(t("language_change_tip")!);
271+
}}
272+
>
273+
{value.title}
274+
</Menu.Item>
275+
))}
276+
</Menu>
277+
}
278+
>
279+
<Button
280+
type="text"
281+
size="small"
282+
iconOnly
283+
icon={<IconLanguage />}
284+
style={{
285+
color: "var(--color-text-1)",
286+
}}
287+
className="!text-lg"
288+
></Button>
289+
</Dropdown>
290+
)}
235291
</Space>
236292
</Layout.Header>
237293
<Layout

src/pkg/config/config.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import ChromeStorage from "./chrome_storage";
33
import { defaultConfig } from "../../../packages/eslint/linter-config";
44
import { FileSystemType } from "@Packages/filesystem/factory";
55
import { MessageQueue } from "@Packages/message/message_queue";
6-
import i18n from "@App/locales/locales";
6+
import i18n, { matchLanguage } from "@App/locales/locales";
77
import dayjs from "dayjs";
88
import { ExtVersion } from "@App/app/const";
99

@@ -235,27 +235,28 @@ export class SystemConfig {
235235
this.set("menu_expand_num", val);
236236
}
237237

238-
async getLanguage(acceptLanguages?: string[]): Promise<string> {
239-
const defaultLanguage = await new Promise<string>(async (resolve) => {
240-
if (!acceptLanguages) {
241-
acceptLanguages = await chrome.i18n.getAcceptLanguages();
238+
async getLanguage() {
239+
if (globalThis.localStorage) {
240+
const cachedLanguage = localStorage.getItem("language");
241+
if (cachedLanguage) {
242+
return cachedLanguage;
242243
}
243-
// 遍历数组寻找匹配语言
244-
for (let i = 0; i < acceptLanguages.length; i += 1) {
245-
const lng = acceptLanguages[i];
246-
if (i18n.hasResourceBundle(lng, "translation")) {
247-
resolve(lng);
248-
break;
249-
}
250-
}
251-
});
252-
return this.get("language", defaultLanguage || chrome.i18n.getUILanguage());
244+
}
245+
let lng = await this.get("language", (await matchLanguage()) || chrome.i18n.getUILanguage());
246+
// 设置进入缓存
247+
if (globalThis.localStorage) {
248+
localStorage.setItem("language", lng);
249+
}
250+
return lng;
253251
}
254252

255253
setLanguage(value: any) {
256254
this.set("language", value);
257255
i18n.changeLanguage(value);
258256
dayjs.locale(value.toLocaleLowerCase());
257+
if (globalThis.localStorage) {
258+
localStorage.setItem("language", value);
259+
}
259260
}
260261

261262
setCheckUpdate(data: { notice: string; version: string; isRead: boolean }) {

0 commit comments

Comments
 (0)