Skip to content

Commit 9d21e59

Browse files
committed
Merge branch 'whitescreen' into release-v4.47.0
2 parents 55c467d + 0c430b4 commit 9d21e59

File tree

8 files changed

+73
-122
lines changed

8 files changed

+73
-122
lines changed

cmd/servewallet/main.go

-12
Original file line numberDiff line numberDiff line change
@@ -103,18 +103,6 @@ func (webdevEnvironment) NativeLocale() string {
103103
if v == "" {
104104
v = os.Getenv("LANG")
105105
}
106-
// try macOS specific AppleLocale
107-
if v == "" && runtime.GOOS == "darwin" {
108-
cmd := exec.Command("defaults", "read", "-g", "AppleLocale") // may return something like en_US@rg=chzzzz
109-
out, err := cmd.Output()
110-
if err == nil {
111-
v = strings.Split(string(out), "@")[0]
112-
}
113-
}
114-
// If still empty, provide a default
115-
if v == "" {
116-
v = "en_US" // Default to English (United States)
117-
}
118106
// Strip charset from the LANG. It is unsupported by JS Date formatting
119107
// used in the frontend and breaks UI in unexpected ways.
120108
// We are always UTF-8 anyway.

frontends/web/src/components/guide/entry.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const Entry = (props: TProps) => {
5555
<div className={[style.entryContent, shown ? style.expanded : ''].join(' ')}>
5656
{shown ? (
5757
<div className="flex-1">
58-
{entry?.text?.trim().split('\n').map((p, idx) => <p key={idx}>{p}</p>)}
58+
{entry.text.trim().split('\n').map((p, idx) => <p key={idx}>{p}</p>)}
5959
{entry.link && (
6060
<p>
6161
<A

frontends/web/src/components/language/language.test.tsx

+1-8
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,7 @@ import { useTranslation } from 'react-i18next';
2222
import { TLanguagesList } from './types';
2323

2424
vi.mock('react-i18next', () => ({
25-
useTranslation: vi.fn(),
26-
hasResourceBundle: vi.fn(),
27-
addResourceBundle: vi.fn(),
28-
changeLanguage: vi.fn()
29-
}));
30-
31-
vi.mock('@/i18n/i18n', () => ({
32-
changei18nLanguage: vi.fn()
25+
useTranslation: vi.fn()
3326
}));
3427

3528
describe('components/language/language', () => {

frontends/web/src/components/language/language.tsx

+3-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import { Dialog } from '@/components/dialog/dialog';
2121
import { defaultLanguages, TActiveLanguageCodes, TLanguagesList } from './types';
2222
import style from './language.module.css';
2323
import { getSelectedIndex } from '@/utils/language';
24-
import { changei18nLanguage } from '@/i18n/i18n';
2524

2625
type TLanguageSwitchProps = {
2726
languages?: TLanguagesList;
@@ -35,10 +34,10 @@ const LanguageSwitch = ({ languages }: TLanguageSwitchProps) => {
3534
const [selectedIndex, setSelectedIndex] = useState<number>(getSelectedIndex(allLanguages, i18n));
3635
const [activeDialog, setActiveDialog] = useState<boolean>(false);
3736

38-
const changeLanguage = async (langCode: TActiveLanguageCodes, index: number) => {
37+
const changeLanguage = (langCode: TActiveLanguageCodes, index: number) => {
3938
setSelectedIndex(index);
4039
setActiveDialog(false);
41-
await changei18nLanguage(langCode);
40+
i18n.changeLanguage(langCode);
4241
};
4342

4443
if (allLanguages.length === 1) {
@@ -107,4 +106,4 @@ const LanguageSwitch = ({ languages }: TLanguageSwitchProps) => {
107106
);
108107
};
109108

110-
export { LanguageSwitch };
109+
export { LanguageSwitch };

frontends/web/src/i18n/config.test.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { languageFromConfig } from './config';
2929
describe('language detector', () => {
3030
it('defaults to english', () => new Promise<void>(done => {
3131
(apiGet as Mock).mockResolvedValue({});
32-
languageFromConfig.detect((lang) => {
32+
languageFromConfig.detect((lang: any) => {
3333
expect(lang).toEqual('en');
3434
done();
3535
});
@@ -43,7 +43,7 @@ describe('language detector', () => {
4343
default: { return Promise.resolve(); }
4444
}
4545
});
46-
languageFromConfig.detect((lang) => {
46+
languageFromConfig.detect((lang: any) => {
4747
expect(lang).toEqual('it');
4848
done();
4949
});
@@ -57,7 +57,7 @@ describe('language detector', () => {
5757
default: { return Promise.resolve(); }
5858
}
5959
});
60-
languageFromConfig.detect((lang) => {
60+
languageFromConfig.detect((lang: any) => {
6161
expect(lang).toEqual('de');
6262
done();
6363
});
@@ -71,7 +71,7 @@ describe('language detector', () => {
7171
default: { return Promise.resolve(); }
7272
}
7373
});
74-
languageFromConfig.detect((lang) => {
74+
languageFromConfig.detect((lang: any) => {
7575
expect(lang).toEqual('en');
7676
done();
7777
});
@@ -85,7 +85,7 @@ describe('language detector', () => {
8585
default: { return Promise.resolve(); }
8686
}
8787
});
88-
languageFromConfig.detect((lang) => {
88+
languageFromConfig.detect((lang: any) => {
8989
expect(lang).toEqual('de');
9090
done();
9191
});
@@ -99,7 +99,7 @@ describe('language detector', () => {
9999
default: { return Promise.resolve(); }
100100
}
101101
});
102-
languageFromConfig.detect((lang) => {
102+
languageFromConfig.detect((lang: any) => {
103103
expect(lang).toEqual('pt-BR');
104104
done();
105105
});

frontends/web/src/i18n/i18n.test.tsx

+3-6
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ vi.mock('@/utils/request', () => ({
2424
}));
2525

2626
import { apiGet, apiPost } from '@/utils/request';
27-
import { changei18nLanguage } from './i18n';
27+
import { i18n } from './i18n';
2828

2929
describe('i18n', () => {
3030
describe('languageChanged', () => {
@@ -37,9 +37,6 @@ describe('i18n', () => {
3737
{ nativeLocale: 'de-DE', newLang: 'de', userLang: null },
3838
{ nativeLocale: 'pt_BR', newLang: 'pt', userLang: null },
3939
{ nativeLocale: 'fr', newLang: 'en', userLang: 'en' },
40-
{ nativeLocale: 'WAGA_WAGA', newLang: 'en', userLang: 'en' }, // unknown locale
41-
{ nativeLocale: '', newLang: 'fr', userLang: 'fr' }, // empty locale
42-
{ nativeLocale: '-_-_', newLang: 'de', userLang: 'de' }, // with invalid locale
4340
];
4441
table.forEach((test) => {
4542
it(`sets userLanguage to ${test.userLang || 'null'} if native-locale is ${test.nativeLocale}`, async () => {
@@ -50,9 +47,9 @@ describe('i18n', () => {
5047
default: { return Promise.resolve(); }
5148
}
5249
});
53-
await changei18nLanguage(test.newLang);
50+
await i18n.changeLanguage(test.newLang);
5451
await waitFor(() => {
55-
expect(apiPost).toHaveBeenCalled();
52+
expect(apiPost).toHaveBeenCalledTimes(1);
5653
expect(apiPost).toHaveBeenCalledWith('config', {
5754
frontend: {},
5855
backend: { userLanguage: test.userLang },

frontends/web/src/i18n/i18n.ts

+58-83
Original file line numberDiff line numberDiff line change
@@ -17,60 +17,36 @@
1717

1818
import i18n from 'i18next';
1919
import { getNativeLocale } from '@/api/nativelocale';
20+
import appTranslationsAR from '@/locales/ar/app.json';
21+
import appTranslationsCS from '@/locales/cs/app.json';
22+
import appTranslationsDE from '@/locales/de/app.json';
23+
import appTranslationsEN from '@/locales/en/app.json';
24+
import appTranslationsFR from '@/locales/fr/app.json';
25+
import appTranslationsJA from '@/locales/ja/app.json';
26+
import appTranslationsRU from '@/locales/ru/app.json';
27+
import appTranslationsMS from '@/locales/ms/app.json';
28+
import appTranslationsNL from '@/locales/nl/app.json';
29+
import appTranslationsPT from '@/locales/pt/app.json';
30+
import appTranslationsHI from '@/locales/hi/app.json';
31+
import appTranslationsBG from '@/locales/bg/app.json';
32+
import appTranslationsTR from '@/locales/tr/app.json';
33+
import appTranslationsZH from '@/locales/zh/app.json';
34+
import appTranslationsFA from '@/locales/fa/app.json';
35+
import appTranslationsES from '@/locales/es/app.json';
36+
import appTranslationsSL from '@/locales/sl/app.json';
37+
import appTranslationsHE from '@/locales/he/app.json';
38+
import appTranslationsIT from '@/locales/it/app.json';
2039
import { languageFromConfig } from './config';
2140
import { localeMainLanguage } from './utils';
2241
import { setConfig } from '@/utils/config';
23-
import appTranslationsEN from '@/locales/en/app.json';
2442

2543
const locizeProjectID = 'fe4e5a24-e4a2-4903-96fc-3d62c11fc502';
2644

27-
let isChangingLanguage = false;
28-
const defaultFallbackLang = 'en';
29-
30-
const languageResources = {
31-
ar: () => import('@/locales/ar/app.json'),
32-
cs: () => import('@/locales/cs/app.json'),
33-
de: () => import('@/locales/de/app.json'),
34-
en: () => Promise.resolve({ default: appTranslationsEN }),
35-
fr: () => import('@/locales/fr/app.json'),
36-
ja: () => import('@/locales/ja/app.json'),
37-
ru: () => import('@/locales/ru/app.json'),
38-
ms: () => import('@/locales/ms/app.json'),
39-
nl: () => import('@/locales/nl/app.json'),
40-
pt: () => import('@/locales/pt/app.json'),
41-
hi: () => import('@/locales/hi/app.json'),
42-
bg: () => import('@/locales/bg/app.json'),
43-
tr: () => import('@/locales/tr/app.json'),
44-
zh: () => import('@/locales/zh/app.json'),
45-
fa: () => import('@/locales/fa/app.json'),
46-
es: () => import('@/locales/es/app.json'),
47-
sl: () => import('@/locales/sl/app.json'),
48-
he: () => import('@/locales/he/app.json'),
49-
it: () => import('@/locales/it/app.json')
50-
};
51-
52-
type LanguageKey = keyof typeof languageResources;
53-
54-
export const loadLanguage = async (language: string) => {
55-
try {
56-
const resources = await languageResources[language as LanguageKey]();
57-
if (!i18n.hasResourceBundle(language, 'app')) {
58-
i18n.addResourceBundle(language, 'app', resources.default || resources);
59-
}
60-
} catch (error) {
61-
console.error(`Failed to load language resources for ${language}:`, error);
62-
}
63-
};
64-
65-
export const changei18nLanguage = async (language: string) => {
66-
await loadLanguage(language);
67-
await i18n.changeLanguage(language);
68-
};
69-
70-
let i18Init = i18n.use(languageFromConfig);
45+
let i18Init = i18n
46+
.use(languageFromConfig);
7147

7248
i18Init.init({
73-
fallbackLng: defaultFallbackLang,
49+
fallbackLng: 'en',
7450

7551
// have a common namespace used around the full app
7652
ns: ['app', 'wallet'],
@@ -83,56 +59,55 @@ i18Init.init({
8359
},
8460

8561
react: {
86-
useSuspense: true // Not using Suspense you will need to handle the not ready state yourself
62+
useSuspense : true, // Not using Suspense you will need to handle the not ready state yourself
8763
},
8864

8965
backend: {
9066
projectId: locizeProjectID,
91-
referenceLng: defaultFallbackLang
92-
}
67+
referenceLng: 'en'
68+
},
9369
});
9470

95-
// always include 'en' so we have a fallback for keys that are not translated
96-
i18n.addResourceBundle(defaultFallbackLang, 'app', appTranslationsEN);
97-
98-
i18n.on('languageChanged', async (lng) => {
99-
// changei18nLanguage triggers languageChanged, thus this check to prevent loop
100-
if (isChangingLanguage) {
101-
return;
102-
}
103-
104-
try {
105-
isChangingLanguage = true;
106-
// Set userLanguage in config back to empty if system locale matches
107-
// the newly selected language lng to make the app use native-locale again.
108-
// This also covers partial matches. For example, if native locale is pt_BR
109-
// and the app has only pt translation, assume they match.
110-
//
111-
// Since userLanguage is stored in the backend config as a string,
112-
// setting it to null here in JS turns it into an empty string "" in Go backend.
113-
// This is ok since we're just checking for a truthy value in the language detector.
114-
const nativeLocale = await getNativeLocale();
71+
i18n.addResourceBundle('ar', 'app', appTranslationsAR);
72+
i18n.addResourceBundle('cs', 'app', appTranslationsCS);
73+
i18n.addResourceBundle('de', 'app', appTranslationsDE);
74+
i18n.addResourceBundle('en', 'app', appTranslationsEN);
75+
i18n.addResourceBundle('fr', 'app', appTranslationsFR);
76+
i18n.addResourceBundle('ja', 'app', appTranslationsJA);
77+
i18n.addResourceBundle('ms', 'app', appTranslationsMS);
78+
i18n.addResourceBundle('nl', 'app', appTranslationsNL);
79+
i18n.addResourceBundle('ru', 'app', appTranslationsRU);
80+
i18n.addResourceBundle('pt', 'app', appTranslationsPT);
81+
i18n.addResourceBundle('hi', 'app', appTranslationsHI);
82+
i18n.addResourceBundle('bg', 'app', appTranslationsBG);
83+
i18n.addResourceBundle('tr', 'app', appTranslationsTR);
84+
i18n.addResourceBundle('zh', 'app', appTranslationsZH);
85+
i18n.addResourceBundle('fa', 'app', appTranslationsFA);
86+
i18n.addResourceBundle('es', 'app', appTranslationsES);
87+
i18n.addResourceBundle('sl', 'app', appTranslationsSL);
88+
i18n.addResourceBundle('he', 'app', appTranslationsHE);
89+
i18n.addResourceBundle('it', 'app', appTranslationsIT);
90+
91+
i18n.on('languageChanged', (lng) => {
92+
// Set userLanguage in config back to empty if system locale matches
93+
// the newly selected language lng to make the app use native-locale again.
94+
// This also covers partial matches. For example, if native locale is pt_BR
95+
// and the app has only pt translation, assume they match.
96+
//
97+
// Since userLanguage is stored in the backend config as a string,
98+
// setting it to null here in JS turns it into an empty string "" in Go backend.
99+
// This is ok since we're just checking for a truthy value in the language detector.
100+
return getNativeLocale().then((nativeLocale) => {
115101
let match = lng === nativeLocale;
116-
117102
if (!match) {
118-
const localeLang = localeMainLanguage(nativeLocale);
103+
// There are too many combinations. So, we compare only the main
104+
// language tag.
119105
const lngLang = localeMainLanguage(lng);
120-
await changei18nLanguage(localeMainLanguage(lng));
106+
const localeLang = localeMainLanguage(nativeLocale);
121107
match = lngLang === localeLang;
122108
}
123-
124109
const uiLang = match ? null : lng;
125110
return setConfig({ backend: { userLanguage: uiLang } });
126-
} finally {
127-
isChangingLanguage = false;
128-
}
129-
});
130-
131-
i18n.on('initialized', () => {
132-
languageFromConfig.detect((lang) => {
133-
if (lang && typeof lang === 'string') {
134-
changei18nLanguage(localeMainLanguage(lang));
135-
}
136111
});
137112
});
138113

frontends/web/src/routes/settings/components/appearance/languageDropdownSetting.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { useTranslation } from 'react-i18next';
1919
import { useLoad } from '@/hooks/api';
2020
import { getNativeLocale } from '@/api/nativelocale';
2121
import { defaultLanguages } from '@/components/language/types';
22-
import { changei18nLanguage } from '@/i18n/i18n';
2322
import { Dropdown } from '@/components/dropdown/dropdown';
2423
import { getSelectedIndex } from '@/utils/language';
2524
import { GlobeDark, GlobeLight } from '@/components/icon/icon';
@@ -45,7 +44,7 @@ export const LanguageDropdownSetting = () => {
4544
className={settingsDropdownStyles.select}
4645
renderOptions={(o) => (o.label)}
4746
options={formattedLanguages}
48-
onChange={(selected) => changei18nLanguage(selected.value)}
47+
onChange={(selected) => i18n.changeLanguage(selected.value)}
4948
value={{ label: selectedLanguage.display, value: selectedLanguage.code }}
5049
/>
5150
}

0 commit comments

Comments
 (0)