fix language change

This commit is contained in:
Ildar Kamalov
2025-08-11 18:38:28 +03:00
parent 8206f3f885
commit 6f696238a0
7 changed files with 75 additions and 21 deletions

View File

@@ -1,3 +1,3 @@
{
"client_settings": "Настройки клиентов"
"privacy_policy": "Политика конфиденциальности"
}

View File

@@ -7,7 +7,7 @@ import escapeRegExp from 'lodash/escapeRegExp';
import React from 'react';
import { compose } from 'redux';
import type { Dispatch } from 'redux';
import intl from 'panel/common/intl';
import intl, { type LocalesType } from 'panel/common/intl';
import {
splitByNewLine,
sortClients,
@@ -23,6 +23,7 @@ import {
SETTINGS_NAMES,
MANUAL_UPDATE_LINK,
DISABLE_PROTECTION_TIMINGS,
THEMES,
} from '../helpers/constants';
import { areEqualVersions } from '../helpers/version';
import { getTlsStatus } from './encryption';
@@ -32,6 +33,7 @@ import { getFilteringStatus, setRules } from './filtering';
type SafeSearchConfig = Record<string, boolean> & { enabled: boolean };
type ToggleSettingKey = keyof typeof SETTINGS_NAMES;
type Theme = typeof THEMES[keyof typeof THEMES];
export const toggleSettingStatus = createAction<{
settingKey: ToggleSettingKey;
@@ -441,13 +443,13 @@ export const testUpstreamWithFormValues = (formValues: any) => async (dispatch:
export const changeLanguageRequest = createAction('CHANGE_LANGUAGE_REQUEST');
export const changeLanguageFailure = createAction('CHANGE_LANGUAGE_FAILURE');
export const changeLanguageSuccess = createAction('CHANGE_LANGUAGE_SUCCESS');
export const changeLanguageSuccess = createAction<{ language: LocalesType }>('CHANGE_LANGUAGE_SUCCESS');
export const changeLanguage = (lang: any) => async (dispatch: any) => {
export const changeLanguage = (lang: LocalesType) => async (dispatch: Dispatch) => {
dispatch(changeLanguageRequest());
try {
await apiClient.changeLanguage({ language: lang });
dispatch(changeLanguageSuccess());
dispatch(changeLanguageSuccess({ language: lang }));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(changeLanguageFailure());
@@ -456,9 +458,9 @@ export const changeLanguage = (lang: any) => async (dispatch: any) => {
export const changeThemeRequest = createAction('CHANGE_THEME_REQUEST');
export const changeThemeFailure = createAction('CHANGE_THEME_FAILURE');
export const changeThemeSuccess = createAction('CHANGE_THEME_SUCCESS');
export const changeThemeSuccess = createAction<{ theme: Theme }>('CHANGE_THEME_SUCCESS');
export const changeTheme = (theme: any) => async (dispatch: any) => {
export const changeTheme = (theme: Theme) => async (dispatch: Dispatch) => {
dispatch(changeThemeRequest());
try {
await apiClient.changeTheme({ theme });

View File

@@ -1,5 +1,7 @@
import axios from 'axios';
import type { LocalesType } from 'panel/common/intl';
import { BASE_URL } from '../../constants';
import { getPathWithQueryString } from '../helpers/helpers';
@@ -7,6 +9,8 @@ import { QUERY_LOGS_PAGE_LIMIT, HTML_PAGES, R_PATH_LAST_PART, THEMES } from '../
import i18n from '../i18n';
import { LANGUAGES } from '../helpers/twosky';
type Theme = typeof THEMES[keyof typeof THEMES];
class Api {
baseUrl: string;
@@ -258,8 +262,7 @@ class Api {
// }
// Language
async changeLanguage(config: any) {
async changeLanguage(config: { language: LocalesType }) {
const profile = await this.getProfile();
profile.language = config.language;
@@ -267,8 +270,7 @@ class Api {
}
// Theme
async changeTheme(config: any) {
async changeTheme(config: { theme: Theme }) {
const profile = await this.getProfile();
profile.theme = config.theme;

View File

@@ -15,6 +15,7 @@ import ptPt from 'panel/__locales/pt-pt.json';
import ru from 'panel/__locales/ru.json';
import zhCn from 'panel/__locales/zh-cn.json';
import zhTw from 'panel/__locales/zh-tw.json';
import { LOCAL_STORAGE_KEYS, LocalStorageHelper } from 'panel/helpers/localStorageHelper';
export type LocalesType = ReturnType<I18nInterface['getUILanguage']>;
@@ -37,14 +38,55 @@ const LOCALES = {
const messages: LocalesTypes = LOCALES;
export const i18n = (lang: LocalesType) => ({
getMessage: (key: string) => messages[lang]?.[key] || '',
getUILanguage: () => (lang.includes('zh') ? 'zh' : lang),
getBaseMessage: (key: string) => messages.en![key] || key,
getBaseUILanguage: () => BASE_LOCALE as LocalesType,
});
const resolveLanguage = (lng: string): LocalesType => {
const l = lng.toLowerCase();
let currentLanguage: LocalesType = BASE_LOCALE as LocalesType;
if (messages[l as LocalesType]) {
return l as LocalesType;
}
// Chinese locales
if (l.startsWith('zh')) {
if (l.includes('tw') || l.includes('hk') || l.includes('mo')) {
return 'zh-tw' as LocalesType;
}
return 'zh-cn' as LocalesType;
}
// Portuguese locales
if (l.startsWith('pt')) {
if (l.includes('br')) {
return 'pt-br' as LocalesType;
}
return 'pt-pt' as LocalesType;
}
// Try base language (e.g., en-us -> en)
const base = l.split('-')[0] as LocalesType;
if (messages[base]) {
return base;
}
return BASE_LOCALE as LocalesType;
};
export const i18n = (lang: LocalesType) => {
const resolved = resolveLanguage(lang);
return {
getMessage: (key: string) => messages[resolved]?.[key] || '',
getUILanguage: () => resolved,
getBaseMessage: (key: string) => messages.en![key] || key,
getBaseUILanguage: () => BASE_LOCALE as LocalesType,
};
};
const detectedLanguage = (
(typeof window !== 'undefined' && typeof localStorage !== 'undefined' && LocalStorageHelper.getItem(LOCAL_STORAGE_KEYS.LANGUAGE)) ||
(typeof navigator !== 'undefined' && (navigator.language as string)) ||
BASE_LOCALE
) as LocalesType;
let currentLanguage: LocalesType = resolveLanguage(detectedLanguage);
let translator = translate.createReactTranslator<any>(i18n(currentLanguage), React, {
tags: [
@@ -77,8 +119,9 @@ const intl = {
getBaseUILanguage: () => BASE_LOCALE as LocalesType,
changeLanguage: (newLang: LocalesType) => {
currentLanguage = newLang;
translator = translate.createReactTranslator<any>(i18n(newLang), React, {
const resolved = resolveLanguage(newLang);
currentLanguage = resolved;
translator = translate.createReactTranslator<any>(i18n(resolved), React, {
tags: [
{
key: 'br',

View File

@@ -7,6 +7,7 @@ import { Dropdown } from 'panel/common/ui/Dropdown';
import { Icon } from 'panel/common/ui/Icon';
import intl, { LocalesType } from 'panel/common/intl';
import { LOCAL_STORAGE_KEYS, LocalStorageHelper } from 'panel/helpers/localStorageHelper';
import { REPOSITORY, PRIVACY_POLICY_LINK, THEMES } from '../../../helpers/constants';
import { LANGUAGES } from '../../../helpers/twosky';
import { setHtmlLangAttr, setUITheme } from '../../../helpers/helpers';
@@ -73,11 +74,11 @@ export const Footer = () => {
};
const changeLanguage = async (newLang: LocalesType) => {
intl.changeLanguage(newLang);
setHtmlLangAttr(newLang);
try {
await dispatch(changeLanguageAction(newLang));
LocalStorageHelper.setItem(LOCAL_STORAGE_KEYS.LANGUAGE, newLang);
window.location.reload();
} catch (error) {
console.error('Failed to save language preference:', error);

View File

@@ -5,6 +5,7 @@ export const LOCAL_STORAGE_KEYS = {
CLIENTS_PAGE_SIZE: 'clients_page_size',
REWRITES_PAGE_SIZE: 'rewrites_page_size',
AUTO_CLIENTS_PAGE_SIZE: 'auto_clients_page_size',
LANGUAGE: 'language',
};
export const LocalStorageHelper = {

View File

@@ -163,6 +163,10 @@ const dashboard = handleActions(
theme: payload.theme,
processingProfile: false,
}),
[actions.changeLanguageSuccess.toString()]: (state, { payload }: any) => ({
...state,
language: payload.language,
}),
[actions.changeThemeSuccess.toString()]: (state, { payload }: any) => ({
...state,
theme: payload.theme,
@@ -188,6 +192,7 @@ const dashboard = handleActions(
supportedTags: [],
name: '',
theme: undefined,
language: undefined,
checkUpdateFlag: false,
},
);