Skip to content

Internationalization and themes ​

Language and theme follow the same principle: each has one source of truth, and changes are reflected immediately in the DOM and component library without requiring a page refresh.

Internationalization structure ​

text
src/config/locale.ts         Language registry
src/locales/                 Application copy
src/plugins/vueI18n/         Creates the i18n instance
src/store/modules/locale.ts  Current language and persistence
src/hooks/useLocale.ts       Runtime switching
ConfigGlobal                 Synchronizes the Element Plus locale

localeRegistry registers application messages, display names, and Element Plus language packs together:

ts
export const localeRegistry = {
  'zh-CN': {
    label: '简体中文',
    messages: zhCNMessages,
    elementLocale: zhCNElement
  },
  en: {
    label: 'English',
    messages: enMessages,
    elementLocale: enElement
  }
} as const

Both the dropdown options and vue-i18n messages are derived from this registry, so the language list does not need to be maintained in three separate files.

Switch languages ​

ts
const { changeLocale } = useLocale()

changeLocale('en')

One call synchronizes:

  • The current vue-i18n locale.
  • Locale Store, persisted to vea-locale-v1.
  • <html lang="...">.
  • The Element Plus locale in ConfigGlobal.
  • The browser title generated by useTitle.

Language switching therefore does not require a refresh.

Add a language ​

Using Japanese as an example:

  1. Create src/locales/ja.ts with the same key structure as the existing languages.
  2. Import element-plus/es/locale/lang/ja from Element Plus.
  3. Add ja to localeRegistry.

LocaleCode, the dropdown options, and the message map automatically receive the new type and data.

Language packs are currently imported synchronously. Asynchronous loading becomes worthwhile only when the number or size of language packs grows substantially; two languages do not justify adding switching states and load-failure branches.

Use translated copy ​

ts
const { t } = useI18n()

const title = computed(() => t('router.dashboard'))

Use functions for form validation messages so a new validation run reads the active language after a locale change:

ts
username: required(() => t('common.required'))

The current route meta.title stores an i18n key. Menus, breadcrumbs, and TagsView should all render it through the same translation logic.

Theme structure ​

Light and dark state is stored in App Store:

ts
const appStore = useAppStore()
appStore.isDark = !appStore.isDark

useTheme watches this state and switches the light or dark class on <html>. The state is persisted in vea-app-v2 and restored after a page refresh.

Only manual light and dark modes are currently supported. There is no follow-system option, so documentation and product settings should not present a third mode that does not exist.

Theme variables ​

Shared variables live in packages/styles/src/var.css:

css
html:root {
  --el-color-primary: #5b5bd6;
  --app-content-bg-color: #f6f7fb;
  --left-menu-bg-color: #fff;
}

html:root.dark {
  --el-color-primary: #818cf8;
  --app-content-bg-color: #0b1120;
  --left-menu-bg-color: #111827;
}

Element Plus variables and semantic layout variables are overridden under the same theme. This avoids isolated color systems for the top navigation, sidebar, and content area.

When introducing a color, define its purpose first—for example, --layout-border-color. Avoid defining a context-free name such as --gray-7 and making every component guess how it should be used.

Theme component boundaries ​

ThemeSwitch can remain in @vea/components because it only accepts controlled state and emits an event. The Admin app still owns Store access and DOM switching. A shared component must not read useAppStore() directly, or another app will be unable to reuse it.

If another application only needs a normal button, it can invoke the theme action directly. It does not have to use the same presentation component merely for consistency.

Keep the core for real business and stay restrained for unknown requirements.