mirror of
https://github.com/chartdb/chartdb.git
synced 2026-05-05 11:49:36 -05:00
fix(i18n): change language keeps selected language also after refreshing the page (#409)
This commit is contained in:
Generated
+10
@@ -44,6 +44,7 @@
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"html-to-image": "^1.11.11",
|
||||
"i18next": "^23.14.0",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"lucide-react": "^0.441.0",
|
||||
"monaco-editor": "^0.52.0",
|
||||
"nanoid": "^5.0.7",
|
||||
@@ -6755,6 +6756,15 @@
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next-browser-languagedetector": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
|
||||
"integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"html-to-image": "^1.11.11",
|
||||
"i18next": "^23.14.0",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"lucide-react": "^0.441.0",
|
||||
"monaco-editor": "^0.52.0",
|
||||
"nanoid": "^5.0.7",
|
||||
|
||||
@@ -26,7 +26,7 @@ export interface SelectBoxOption {
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface SelectBoxProps {
|
||||
export interface SelectBoxProps {
|
||||
options: SelectBoxOption[];
|
||||
value?: string[] | string;
|
||||
onChange?: (values: string[] | string) => void;
|
||||
|
||||
+11
-9
@@ -1,5 +1,6 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
import type { LanguageMetadata } from './types';
|
||||
import { en, enMetadata } from './locales/en';
|
||||
import { es, esMetadata } from './locales/es';
|
||||
@@ -44,14 +45,15 @@ const resources = {
|
||||
zh_TW,
|
||||
};
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources,
|
||||
lng: enMetadata.code,
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
fallbackLng: enMetadata.code,
|
||||
debug: false,
|
||||
});
|
||||
i18n.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
fallbackLng: enMetadata.code,
|
||||
debug: false,
|
||||
});
|
||||
|
||||
export { i18n };
|
||||
|
||||
@@ -28,7 +28,6 @@ export const de: LanguageTranslation = {
|
||||
show_cardinality: 'Kardinalität anzeigen',
|
||||
zoom_on_scroll: 'Zoom beim Scrollen',
|
||||
theme: 'Stil',
|
||||
change_language: 'Sprache',
|
||||
show_dependencies: 'Abhängigkeiten anzeigen',
|
||||
hide_dependencies: 'Abhängigkeiten ausblenden',
|
||||
},
|
||||
@@ -382,10 +381,15 @@ export const de: LanguageTranslation = {
|
||||
tool_tips: {
|
||||
double_click_to_edit: 'Doppelklicken zum Bearbeiten',
|
||||
},
|
||||
|
||||
language_select: {
|
||||
change_language: 'Sprache',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const deMetadata: LanguageMetadata = {
|
||||
name: 'Deutsch',
|
||||
name: 'German',
|
||||
nativeName: 'Deutsch',
|
||||
code: 'de',
|
||||
};
|
||||
|
||||
@@ -28,7 +28,6 @@ export const en = {
|
||||
show_cardinality: 'Show Cardinality',
|
||||
zoom_on_scroll: 'Zoom on Scroll',
|
||||
theme: 'Theme',
|
||||
change_language: 'Language',
|
||||
show_dependencies: 'Show Dependencies',
|
||||
hide_dependencies: 'Hide Dependencies',
|
||||
},
|
||||
@@ -377,10 +376,15 @@ export const en = {
|
||||
tool_tips: {
|
||||
double_click_to_edit: 'Double-click to edit',
|
||||
},
|
||||
|
||||
language_select: {
|
||||
change_language: 'Language',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const enMetadata: LanguageMetadata = {
|
||||
name: 'English',
|
||||
nativeName: 'English',
|
||||
code: 'en',
|
||||
};
|
||||
|
||||
@@ -28,7 +28,6 @@ export const es: LanguageTranslation = {
|
||||
hide_sidebar: 'Ocultar Barra Lateral',
|
||||
zoom_on_scroll: 'Zoom al Desplazarse',
|
||||
theme: 'Tema',
|
||||
change_language: 'Idioma',
|
||||
show_dependencies: 'Mostrar dependencias',
|
||||
hide_dependencies: 'Ocultar dependencias',
|
||||
},
|
||||
@@ -382,10 +381,15 @@ export const es: LanguageTranslation = {
|
||||
tool_tips: {
|
||||
double_click_to_edit: 'Doble clic para editar',
|
||||
},
|
||||
|
||||
language_select: {
|
||||
change_language: 'Idioma',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const esMetadata: LanguageMetadata = {
|
||||
name: 'Español',
|
||||
name: 'Spanish',
|
||||
nativeName: 'Español',
|
||||
code: 'es',
|
||||
};
|
||||
|
||||
@@ -28,7 +28,6 @@ export const fr: LanguageTranslation = {
|
||||
show_cardinality: 'Afficher la Cardinalité',
|
||||
zoom_on_scroll: 'Zoom sur le Défilement',
|
||||
theme: 'Thème',
|
||||
change_language: 'Langue',
|
||||
show_dependencies: 'Afficher les Dépendances',
|
||||
hide_dependencies: 'Masquer les Dépendances',
|
||||
},
|
||||
@@ -384,10 +383,15 @@ export const fr: LanguageTranslation = {
|
||||
tool_tips: {
|
||||
double_click_to_edit: 'Double-cliquez pour modifier',
|
||||
},
|
||||
|
||||
language_select: {
|
||||
change_language: 'Langue',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const frMetadata: LanguageMetadata = {
|
||||
name: 'Français',
|
||||
name: 'French',
|
||||
nativeName: 'Français',
|
||||
code: 'fr',
|
||||
};
|
||||
|
||||
@@ -28,7 +28,6 @@ export const hi: LanguageTranslation = {
|
||||
show_cardinality: 'कार्डिनैलिटी दिखाएँ',
|
||||
zoom_on_scroll: 'स्क्रॉल पर ज़ूम',
|
||||
theme: 'थीम',
|
||||
change_language: 'भाषा बदलें',
|
||||
show_dependencies: 'निर्भरता दिखाएँ',
|
||||
hide_dependencies: 'निर्भरता छिपाएँ',
|
||||
},
|
||||
@@ -384,10 +383,15 @@ export const hi: LanguageTranslation = {
|
||||
tool_tips: {
|
||||
double_click_to_edit: 'संपादित करने के लिए डबल-क्लिक करें',
|
||||
},
|
||||
|
||||
language_select: {
|
||||
change_language: 'भाषा बदलें',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const hiMetadata: LanguageMetadata = {
|
||||
name: 'Hindi',
|
||||
nativeName: 'हिन्दी',
|
||||
code: 'hi',
|
||||
};
|
||||
|
||||
@@ -28,7 +28,6 @@ export const ja: LanguageTranslation = {
|
||||
show_cardinality: 'カーディナリティを表示',
|
||||
zoom_on_scroll: 'スクロールでズーム',
|
||||
theme: 'テーマ',
|
||||
change_language: '言語',
|
||||
// TODO: Translate
|
||||
show_dependencies: 'Show Dependencies',
|
||||
hide_dependencies: 'Hide Dependencies',
|
||||
@@ -386,10 +385,15 @@ export const ja: LanguageTranslation = {
|
||||
tool_tips: {
|
||||
double_click_to_edit: 'ダブルクリックして編集',
|
||||
},
|
||||
|
||||
language_select: {
|
||||
change_language: '言語',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const jaMetadata: LanguageMetadata = {
|
||||
name: 'Japanese',
|
||||
nativeName: '日本語',
|
||||
code: 'ja',
|
||||
};
|
||||
|
||||
@@ -28,7 +28,6 @@ export const ko_KR: LanguageTranslation = {
|
||||
show_cardinality: '카디널리티 보이기',
|
||||
zoom_on_scroll: '스크롤 시 확대',
|
||||
theme: '테마',
|
||||
change_language: '언어/Language',
|
||||
show_dependencies: '종속성 보이기',
|
||||
hide_dependencies: '종속성 숨기기',
|
||||
},
|
||||
@@ -380,10 +379,15 @@ export const ko_KR: LanguageTranslation = {
|
||||
tool_tips: {
|
||||
double_click_to_edit: '더블클릭하여 편집',
|
||||
},
|
||||
|
||||
language_select: {
|
||||
change_language: '언어',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ko_KRMetadata: LanguageMetadata = {
|
||||
name: '한국어',
|
||||
name: 'Korean',
|
||||
nativeName: '한국어',
|
||||
code: 'ko_KR',
|
||||
};
|
||||
|
||||
@@ -28,7 +28,6 @@ export const pt_BR: LanguageTranslation = {
|
||||
show_cardinality: 'Mostrar Cardinalidade',
|
||||
zoom_on_scroll: 'Zoom ao Rolar',
|
||||
theme: 'Tema',
|
||||
change_language: 'Idioma',
|
||||
show_dependencies: 'Mostrar Dependências',
|
||||
hide_dependencies: 'Ocultar Dependências',
|
||||
},
|
||||
@@ -381,10 +380,15 @@ export const pt_BR: LanguageTranslation = {
|
||||
tool_tips: {
|
||||
double_click_to_edit: 'Duplo clique para editar',
|
||||
},
|
||||
|
||||
language_select: {
|
||||
change_language: 'Idioma',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const pt_BRMetadata: LanguageMetadata = {
|
||||
name: 'Portuguese',
|
||||
nativeName: 'Português',
|
||||
code: 'pt_BR',
|
||||
};
|
||||
|
||||
@@ -28,7 +28,6 @@ export const ru: LanguageTranslation = {
|
||||
show_cardinality: 'Показать множественность связи',
|
||||
zoom_on_scroll: 'Увеличение при прокрутке',
|
||||
theme: 'Тема',
|
||||
change_language: 'Сменить язык',
|
||||
show_dependencies: 'Показать зависимости',
|
||||
hide_dependencies: 'Скрыть зависимости',
|
||||
},
|
||||
@@ -376,10 +375,15 @@ export const ru: LanguageTranslation = {
|
||||
tool_tips: {
|
||||
double_click_to_edit: 'Кликните дважды, чтобы изменить',
|
||||
},
|
||||
|
||||
language_select: {
|
||||
change_language: 'Сменить язык',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ruMetadata: LanguageMetadata = {
|
||||
name: 'Russian',
|
||||
nativeName: 'Русский',
|
||||
code: 'ru',
|
||||
};
|
||||
|
||||
@@ -28,7 +28,6 @@ export const uk: LanguageTranslation = {
|
||||
show_cardinality: 'Показати кардинальність',
|
||||
zoom_on_scroll: 'Збільшити прокручування',
|
||||
theme: 'Тема',
|
||||
change_language: 'Мова',
|
||||
show_dependencies: 'Показати залежності',
|
||||
hide_dependencies: 'Приховати залежності',
|
||||
},
|
||||
@@ -381,10 +380,15 @@ export const uk: LanguageTranslation = {
|
||||
tool_tips: {
|
||||
double_click_to_edit: 'Двойной клик для редактирования',
|
||||
},
|
||||
|
||||
language_select: {
|
||||
change_language: 'Мова',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ukMetadata: LanguageMetadata = {
|
||||
name: 'Українська',
|
||||
name: 'Ukrainian',
|
||||
nativeName: 'Українська',
|
||||
code: 'uk',
|
||||
};
|
||||
|
||||
@@ -28,7 +28,6 @@ export const zh_CN: LanguageTranslation = {
|
||||
show_cardinality: '展示基数',
|
||||
zoom_on_scroll: '滚动缩放',
|
||||
theme: '主题',
|
||||
change_language: '语言',
|
||||
show_dependencies: '展示依赖',
|
||||
hide_dependencies: '隐藏依赖',
|
||||
},
|
||||
@@ -371,10 +370,15 @@ export const zh_CN: LanguageTranslation = {
|
||||
tool_tips: {
|
||||
double_click_to_edit: '双击编辑',
|
||||
},
|
||||
|
||||
language_select: {
|
||||
change_language: '语言',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const zh_CNMetadata: LanguageMetadata = {
|
||||
name: '简体中文',
|
||||
name: 'Chinese',
|
||||
nativeName: '简体中文',
|
||||
code: 'zh_CN',
|
||||
};
|
||||
|
||||
@@ -28,7 +28,6 @@ export const zh_TW: LanguageTranslation = {
|
||||
show_cardinality: '顯示基數',
|
||||
zoom_on_scroll: '滾動縮放',
|
||||
theme: '主題',
|
||||
change_language: '變更語言',
|
||||
show_dependencies: '顯示相依性',
|
||||
hide_dependencies: '隱藏相依性',
|
||||
},
|
||||
@@ -370,10 +369,15 @@ export const zh_TW: LanguageTranslation = {
|
||||
tool_tips: {
|
||||
double_click_to_edit: '雙擊以編輯',
|
||||
},
|
||||
|
||||
language_select: {
|
||||
change_language: '變更語言',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const zh_TWMetadata: LanguageMetadata = {
|
||||
name: '繁體中文',
|
||||
nativeName: '繁體中文',
|
||||
name: 'Traditional Chinese',
|
||||
code: 'zh_TW',
|
||||
};
|
||||
|
||||
@@ -4,5 +4,6 @@ export type LanguageTranslation = typeof en;
|
||||
|
||||
export type LanguageMetadata = {
|
||||
name: string;
|
||||
nativeName: string;
|
||||
code: string;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Button } from '@/components/button/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/dropdown-menu/dropdown-menu';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Globe } from 'lucide-react';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/tooltip/tooltip';
|
||||
import type {
|
||||
SelectBoxOption,
|
||||
SelectBoxProps,
|
||||
} from '@/components/select-box/select-box';
|
||||
import { SelectBox } from '@/components/select-box/select-box';
|
||||
import { languages } from '@/i18n/i18n';
|
||||
|
||||
export interface LanguageNavProps {}
|
||||
export const LanguageNav: React.FC<LanguageNavProps> = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
const languagesOptions: SelectBoxOption[] = languages.map((lang) => ({
|
||||
label: lang.nativeName,
|
||||
value: lang.code,
|
||||
description: `(${lang.name})`,
|
||||
}));
|
||||
|
||||
const handleLanguageChange: SelectBoxProps['onChange'] = useCallback(
|
||||
(language: string | string[]) => {
|
||||
i18n.changeLanguage(language as string);
|
||||
},
|
||||
[i18n]
|
||||
);
|
||||
|
||||
const language = useMemo(() => {
|
||||
return i18n.languages
|
||||
.map((lang) => languagesOptions.find((opt) => opt.value === lang))
|
||||
.find((opt) => opt !== undefined)?.value;
|
||||
}, [i18n, languagesOptions]);
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="size-6 rounded-full md:size-8"
|
||||
>
|
||||
<Globe className="size-3.5 md:size-4" />
|
||||
<span className="sr-only">Change language</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{t('language_select.change_language')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenuContent className="w-56">
|
||||
<div className="p-2">
|
||||
<SelectBox
|
||||
className="flex h-8 min-h-8 w-full"
|
||||
options={languagesOptions}
|
||||
value={language}
|
||||
onChange={handleLanguageChange}
|
||||
/>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
@@ -33,8 +33,8 @@ import { useTheme } from '@/hooks/use-theme';
|
||||
import { useLocalConfig } from '@/hooks/use-local-config';
|
||||
import { DiagramName } from './diagram-name';
|
||||
import { LastSaved } from './last-saved';
|
||||
import { languages } from '@/i18n/i18n';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { LanguageNav } from './language-nav/language-nav';
|
||||
|
||||
export interface TopNavbarProps {}
|
||||
|
||||
@@ -62,7 +62,7 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
showDependenciesOnCanvas,
|
||||
} = useLocalConfig();
|
||||
const { effectiveTheme } = useTheme();
|
||||
const { t, i18n } = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
const { redo, undo, hasRedo, hasUndo } = useHistory();
|
||||
const { isMd: isDesktop } = useBreakpoint('md');
|
||||
const { config, updateConfig } = useConfig();
|
||||
@@ -198,13 +198,6 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
|
||||
const emojiAI = '✨';
|
||||
|
||||
const changeLanguage = useCallback(
|
||||
(language: string) => {
|
||||
i18n.changeLanguage(language);
|
||||
},
|
||||
[i18n]
|
||||
);
|
||||
|
||||
return (
|
||||
<nav className="flex flex-col justify-between border-b px-3 md:h-12 md:flex-row md:items-center md:px-4">
|
||||
<div className="flex flex-1 flex-col justify-between gap-x-3 md:flex-row md:justify-normal">
|
||||
@@ -225,7 +218,10 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
/>
|
||||
</a>
|
||||
{!isDesktop ? (
|
||||
<div className="flex items-center">{renderStars()}</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{renderStars()}
|
||||
<LanguageNav />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -529,27 +525,6 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
</MenubarCheckboxItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>
|
||||
{t('menu.view.change_language')}
|
||||
</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
{languages.map((language) => (
|
||||
<MenubarCheckboxItem
|
||||
key={language.code}
|
||||
onClick={() =>
|
||||
changeLanguage(language.code)
|
||||
}
|
||||
checked={
|
||||
i18n.language === language.code
|
||||
}
|
||||
>
|
||||
{language.name}
|
||||
</MenubarCheckboxItem>
|
||||
))}
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
|
||||
@@ -587,6 +562,7 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
<div className="hidden flex-1 items-center justify-end gap-2 sm:flex">
|
||||
<LastSaved />
|
||||
{renderStars()}
|
||||
<LanguageNav />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user