feat(i18n): add full-stack internationalization support
Add languages and entity_translations tables to database schema Implement localized queries and translation management in backend Integrate frontend i18n and add translation UI components
This commit is contained in:
@@ -1,21 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import AppShell from './components/AppShell.vue';
|
||||
import { api, getAuthToken, onAuthTokenChange, setAuthToken, type AuthUser } from './services/api';
|
||||
import { getCurrentLocale, onLocaleChange, setCurrentLocale } from './i18n';
|
||||
import { api, getAuthToken, onAuthTokenChange, setAuthToken, type AuthUser, type Language } from './services/api';
|
||||
|
||||
const navItems = [
|
||||
{ label: 'Pokemon', to: '/pokemon' },
|
||||
{ label: '栖息地', to: '/habitats' },
|
||||
{ label: '物品', to: '/items' },
|
||||
{ label: '材料单', to: '/recipes' },
|
||||
{ label: 'CheckList', to: '/checklist' },
|
||||
{ label: '管理', to: '/admin' }
|
||||
];
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
const router = useRouter();
|
||||
const currentUser = ref<AuthUser | null>(null);
|
||||
const languages = ref<Language[]>([
|
||||
{ code: 'en', name: 'English', enabled: true, isDefault: true, sortOrder: 10 },
|
||||
{ code: 'zh-CN', name: '简体中文', enabled: true, isDefault: false, sortOrder: 20 }
|
||||
]);
|
||||
let removeAuthListener: (() => void) | null = null;
|
||||
let removeLocaleListener: (() => void) | null = null;
|
||||
|
||||
const navItems = computed(() => [
|
||||
{ label: t('nav.pokemon'), to: '/pokemon' },
|
||||
{ label: t('nav.habitats'), to: '/habitats' },
|
||||
{ label: t('nav.items'), to: '/items' },
|
||||
{ label: t('nav.recipes'), to: '/recipes' },
|
||||
{ label: t('nav.checklist'), to: '/checklist' },
|
||||
{ label: t('nav.admin'), to: '/admin' }
|
||||
]);
|
||||
|
||||
async function loadCurrentUser() {
|
||||
if (!getAuthToken()) {
|
||||
@@ -44,20 +53,51 @@ async function logout() {
|
||||
await router.push('/pokemon');
|
||||
}
|
||||
|
||||
async function loadLanguages() {
|
||||
try {
|
||||
const loadedLanguages = await api.languages();
|
||||
if (loadedLanguages.length) {
|
||||
languages.value = loadedLanguages;
|
||||
}
|
||||
|
||||
if (!languages.value.some((language) => language.code === getCurrentLocale() && language.enabled)) {
|
||||
setCurrentLocale('en');
|
||||
}
|
||||
} catch {
|
||||
// Keep the built-in language list when the API is not ready yet.
|
||||
}
|
||||
}
|
||||
|
||||
function updateLocale(value: string) {
|
||||
setCurrentLocale(value);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void loadLanguages();
|
||||
void loadCurrentUser();
|
||||
removeAuthListener = onAuthTokenChange(() => {
|
||||
void loadCurrentUser();
|
||||
});
|
||||
removeLocaleListener = onLocaleChange(() => {
|
||||
void loadLanguages();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
removeAuthListener?.();
|
||||
removeLocaleListener?.();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppShell :current-user="currentUser" :nav-items="navItems" @logout="logout">
|
||||
<RouterView />
|
||||
<AppShell
|
||||
:current-user="currentUser"
|
||||
:languages="languages"
|
||||
:locale="locale"
|
||||
:nav-items="navItems"
|
||||
@logout="logout"
|
||||
@update:locale="updateLocale"
|
||||
>
|
||||
<RouterView :key="locale" />
|
||||
</AppShell>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user