Browse Source
feat: core components support simple locale switching (#4273)
* feat: core components support simple locale switching
* fix: test error
* fix: test error
pull/4280/head
Vben
1 year ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with
78 additions and
32 deletions
-
docs/package.json
-
docs/src/en/guide/in-depth/theme.md
-
docs/src/guide/in-depth/theme.md
-
packages/@core/base/design/src/design-tokens/default/index.css
-
packages/@core/composables/src/index.ts
-
packages/@core/composables/src/use-simple-locale/README.md
-
packages/@core/composables/src/use-simple-locale/index.ts
-
packages/@core/composables/src/use-simple-locale/messages.ts
-
packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts
-
packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts
-
packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue
-
packages/@core/ui-kit/popup-ui/src/modal/__tests__/modal-api.test.ts
-
packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts
-
packages/@core/ui-kit/popup-ui/src/modal/modal.vue
-
packages/@core/ui-kit/shadcn-ui/src/components/logo/logo.vue
-
packages/locales/package.json
-
packages/locales/src/i18n.ts
-
pnpm-lock.yaml
|
|
|
@ -11,7 +11,6 @@ |
|
|
|
"@vben-core/shadcn-ui": "workspace:*", |
|
|
|
"@vben/common-ui": "workspace:*", |
|
|
|
"@vben/styles": "workspace:*", |
|
|
|
"@vueuse/core": "^11.0.3", |
|
|
|
"lucide-vue-next": "^0.436.0", |
|
|
|
"markdown-it": "^14.1.0", |
|
|
|
"medium-zoom": "^1.1.0", |
|
|
|
@ -19,7 +18,6 @@ |
|
|
|
}, |
|
|
|
"devDependencies": { |
|
|
|
"@nolebase/vitepress-plugin-git-changelog": "^2.4.0", |
|
|
|
"@types/markdown-it": "^14.1.2", |
|
|
|
"@vben/vite-config": "workspace:*", |
|
|
|
"@vite-pwa/vitepress": "^0.5.0", |
|
|
|
"vitepress": "^1.3.4", |
|
|
|
|
|
|
|
@ -53,7 +53,7 @@ You can check the list below to understand all the available variables. |
|
|
|
|
|
|
|
/* Theme Colors */ |
|
|
|
|
|
|
|
--primary: 211 91% 39%; |
|
|
|
--primary: 231 98% 65%; |
|
|
|
--primary-foreground: 0 0% 98%; |
|
|
|
|
|
|
|
/* Used for destructive actions such as <Button variant="destructive"> */ |
|
|
|
@ -351,7 +351,7 @@ type BuiltinThemeType = |
|
|
|
|
|
|
|
/* Theme Colors */ |
|
|
|
|
|
|
|
--primary: 211 91% 39%; |
|
|
|
--primary: 231 98% 65%; |
|
|
|
--primary-foreground: 0 0% 98%; |
|
|
|
|
|
|
|
/* Used for destructive actions such as <Button variant="destructive"> */ |
|
|
|
|
|
|
|
@ -53,7 +53,7 @@ css 变量内的颜色,必须使用 `hsl` 格式,如 `0 0% 100%`,不需要 |
|
|
|
|
|
|
|
/* 主题颜色 */ |
|
|
|
|
|
|
|
--primary: 211 91% 39%; |
|
|
|
--primary: 231 98% 65%; |
|
|
|
--primary-foreground: 0 0% 98%; |
|
|
|
|
|
|
|
/* Used for destructive actions such as <Button variant="destructive"> */ |
|
|
|
@ -351,7 +351,7 @@ type BuiltinThemeType = |
|
|
|
|
|
|
|
/* 主题颜色 */ |
|
|
|
|
|
|
|
--primary: 211 91% 39%; |
|
|
|
--primary: 231 98% 65%; |
|
|
|
--primary-foreground: 0 0% 98%; |
|
|
|
|
|
|
|
/* Used for destructive actions such as <Button variant="destructive"> */ |
|
|
|
|
|
|
|
@ -28,7 +28,7 @@ |
|
|
|
|
|
|
|
/* 主题颜色 */ |
|
|
|
|
|
|
|
--primary: 211 91% 39%; |
|
|
|
--primary: 231 98% 65%; |
|
|
|
--primary-foreground: 0 0% 98%; |
|
|
|
|
|
|
|
/* Used for destructive actions such as <Button variant="destructive"> */ |
|
|
|
|
|
|
|
@ -2,6 +2,7 @@ export * from './use-content-style'; |
|
|
|
export * from './use-is-mobile'; |
|
|
|
export * from './use-namespace'; |
|
|
|
export * from './use-priority-value'; |
|
|
|
export * from './use-simple-locale'; |
|
|
|
export * from './use-sortable'; |
|
|
|
export { |
|
|
|
useEmitAsProps, |
|
|
|
|
|
|
|
@ -0,0 +1,3 @@ |
|
|
|
# Simple i18n |
|
|
|
|
|
|
|
Simple i18 implementation |
|
|
|
@ -0,0 +1,25 @@ |
|
|
|
import { computed, ref } from 'vue'; |
|
|
|
|
|
|
|
import { createSharedComposable } from '@vueuse/core'; |
|
|
|
|
|
|
|
import { getMessages, type Locale } from './messages'; |
|
|
|
|
|
|
|
export const useSimpleLocale = createSharedComposable(() => { |
|
|
|
const currentLocale = ref<Locale>('zh-CN'); |
|
|
|
|
|
|
|
const setSimpleLocale = (locale: Locale) => { |
|
|
|
currentLocale.value = locale; |
|
|
|
}; |
|
|
|
|
|
|
|
const $t = computed(() => { |
|
|
|
const localeMessages = getMessages(currentLocale.value); |
|
|
|
return (key: string) => { |
|
|
|
return localeMessages[key] || key; |
|
|
|
}; |
|
|
|
}); |
|
|
|
return { |
|
|
|
$t, |
|
|
|
currentLocale, |
|
|
|
setSimpleLocale, |
|
|
|
}; |
|
|
|
}); |
|
|
|
@ -0,0 +1,14 @@ |
|
|
|
export type Locale = 'en-US' | 'zh-CN'; |
|
|
|
|
|
|
|
export const messages: Record<Locale, Record<string, string>> = { |
|
|
|
'en-US': { |
|
|
|
cancel: 'Cancel', |
|
|
|
confirm: 'Confirm', |
|
|
|
}, |
|
|
|
'zh-CN': { |
|
|
|
cancel: '取消', |
|
|
|
confirm: '确认', |
|
|
|
}, |
|
|
|
}; |
|
|
|
|
|
|
|
export const getMessages = (locale: Locale) => messages[locale]; |
|
|
|
@ -44,8 +44,8 @@ describe('drawerApi', () => { |
|
|
|
|
|
|
|
it('should initialize with default state', () => { |
|
|
|
expect(drawerState.isOpen).toBe(false); |
|
|
|
expect(drawerState.cancelText).toBe('取消'); |
|
|
|
expect(drawerState.confirmText).toBe('确定'); |
|
|
|
expect(drawerState.cancelText).toBe(undefined); |
|
|
|
expect(drawerState.confirmText).toBe(undefined); |
|
|
|
}); |
|
|
|
|
|
|
|
it('should open the drawer', () => { |
|
|
|
|
|
|
|
@ -28,12 +28,10 @@ export class DrawerApi { |
|
|
|
} = options; |
|
|
|
|
|
|
|
const defaultState: DrawerState = { |
|
|
|
cancelText: '取消', |
|
|
|
closable: true, |
|
|
|
closeOnClickModal: true, |
|
|
|
closeOnPressEscape: true, |
|
|
|
confirmLoading: false, |
|
|
|
confirmText: '确定', |
|
|
|
footer: true, |
|
|
|
isOpen: false, |
|
|
|
loading: false, |
|
|
|
|
|
|
|
@ -3,7 +3,11 @@ import type { DrawerProps, ExtendedDrawerApi } from './drawer'; |
|
|
|
|
|
|
|
import { ref, watch } from 'vue'; |
|
|
|
|
|
|
|
import { useIsMobile, usePriorityValue } from '@vben-core/composables'; |
|
|
|
import { |
|
|
|
useIsMobile, |
|
|
|
usePriorityValue, |
|
|
|
useSimpleLocale, |
|
|
|
} from '@vben-core/composables'; |
|
|
|
import { Info, X } from '@vben-core/icons'; |
|
|
|
import { |
|
|
|
Sheet, |
|
|
|
@ -34,7 +38,7 @@ const props = withDefaults(defineProps<Props>(), { |
|
|
|
}); |
|
|
|
|
|
|
|
const wrapperRef = ref<HTMLElement>(); |
|
|
|
|
|
|
|
const { $t } = useSimpleLocale(); |
|
|
|
const { isMobile } = useIsMobile(); |
|
|
|
const state = props.drawerApi?.useStore?.(); |
|
|
|
|
|
|
|
@ -165,7 +169,7 @@ function pointerDownOutside(e: Event) { |
|
|
|
<slot name="footer"> |
|
|
|
<VbenButton variant="ghost" @click="() => drawerApi?.onCancel()"> |
|
|
|
<slot name="cancelText"> |
|
|
|
{{ cancelText }} |
|
|
|
{{ cancelText || $t('cancel') }} |
|
|
|
</slot> |
|
|
|
</VbenButton> |
|
|
|
<VbenButton |
|
|
|
@ -173,7 +177,7 @@ function pointerDownOutside(e: Event) { |
|
|
|
@click="() => drawerApi?.onConfirm()" |
|
|
|
> |
|
|
|
<slot name="confirmText"> |
|
|
|
{{ confirmText }} |
|
|
|
{{ confirmText || $t('confirm') }} |
|
|
|
</slot> |
|
|
|
</VbenButton> |
|
|
|
</slot> |
|
|
|
|
|
|
|
@ -44,8 +44,8 @@ describe('modalApi', () => { |
|
|
|
|
|
|
|
it('should initialize with default state', () => { |
|
|
|
expect(modalState.isOpen).toBe(false); |
|
|
|
expect(modalState.cancelText).toBe('取消'); |
|
|
|
expect(modalState.confirmText).toBe('确定'); |
|
|
|
expect(modalState.cancelText).toBe(undefined); |
|
|
|
expect(modalState.confirmText).toBe(undefined); |
|
|
|
}); |
|
|
|
|
|
|
|
it('should open the modal', () => { |
|
|
|
|
|
|
|
@ -28,12 +28,10 @@ export class ModalApi { |
|
|
|
} = options; |
|
|
|
|
|
|
|
const defaultState: ModalState = { |
|
|
|
cancelText: '取消', |
|
|
|
centered: false, |
|
|
|
closeOnClickModal: true, |
|
|
|
closeOnPressEscape: true, |
|
|
|
confirmLoading: false, |
|
|
|
confirmText: '确定', |
|
|
|
draggable: false, |
|
|
|
footer: true, |
|
|
|
fullscreen: false, |
|
|
|
|
|
|
|
@ -3,7 +3,11 @@ import type { ExtendedModalApi, ModalProps } from './modal'; |
|
|
|
|
|
|
|
import { computed, nextTick, ref, watch } from 'vue'; |
|
|
|
|
|
|
|
import { useIsMobile, usePriorityValue } from '@vben-core/composables'; |
|
|
|
import { |
|
|
|
useIsMobile, |
|
|
|
usePriorityValue, |
|
|
|
useSimpleLocale, |
|
|
|
} from '@vben-core/composables'; |
|
|
|
import { Expand, Info, Shrink } from '@vben-core/icons'; |
|
|
|
import { |
|
|
|
Dialog, |
|
|
|
@ -44,6 +48,7 @@ const dialogRef = ref(); |
|
|
|
const headerRef = ref(); |
|
|
|
const footerRef = ref(); |
|
|
|
|
|
|
|
const { $t } = useSimpleLocale(); |
|
|
|
const { isMobile } = useIsMobile(); |
|
|
|
const state = props.modalApi?.useStore?.(); |
|
|
|
|
|
|
|
@ -235,7 +240,7 @@ function pointerDownOutside(e: Event) { |
|
|
|
<slot name="footer"> |
|
|
|
<VbenButton variant="ghost" @click="() => modalApi?.onCancel()"> |
|
|
|
<slot name="cancelText"> |
|
|
|
{{ cancelText }} |
|
|
|
{{ cancelText || $t('cancel') }} |
|
|
|
</slot> |
|
|
|
</VbenButton> |
|
|
|
<VbenButton |
|
|
|
@ -243,7 +248,7 @@ function pointerDownOutside(e: Event) { |
|
|
|
@click="() => modalApi?.onConfirm()" |
|
|
|
> |
|
|
|
<slot name="confirmText"> |
|
|
|
{{ confirmText }} |
|
|
|
{{ confirmText || $t('confirm') }} |
|
|
|
</slot> |
|
|
|
</VbenButton> |
|
|
|
</slot> |
|
|
|
|
|
|
|
@ -53,10 +53,7 @@ withDefaults(defineProps<Props>(), { |
|
|
|
:width="logoSize" |
|
|
|
class="relative rounded-none bg-transparent" |
|
|
|
/> |
|
|
|
<span |
|
|
|
v-if="!collapsed" |
|
|
|
class="text-primary dark:text-foreground truncate text-nowrap" |
|
|
|
> |
|
|
|
<span v-if="!collapsed" class="text-foreground truncate text-nowrap"> |
|
|
|
{{ text }} |
|
|
|
</span> |
|
|
|
</a> |
|
|
|
|
|
|
|
@ -21,6 +21,7 @@ |
|
|
|
}, |
|
|
|
"dependencies": { |
|
|
|
"@intlify/core-base": "^9.14.0", |
|
|
|
"@vben-core/composables": "workspace:*", |
|
|
|
"vue": "^3.4.38", |
|
|
|
"vue-i18n": "^9.14.0" |
|
|
|
} |
|
|
|
|
|
|
|
@ -10,6 +10,8 @@ import type { |
|
|
|
import { type App, unref } from 'vue'; |
|
|
|
import { createI18n } from 'vue-i18n'; |
|
|
|
|
|
|
|
import { useSimpleLocale } from '@vben-core/composables'; |
|
|
|
|
|
|
|
const i18n = createI18n({ |
|
|
|
globalInjection: true, |
|
|
|
legacy: false, |
|
|
|
@ -19,6 +21,8 @@ const i18n = createI18n({ |
|
|
|
|
|
|
|
const modules = import.meta.glob('./langs/*.json'); |
|
|
|
|
|
|
|
const { setSimpleLocale } = useSimpleLocale(); |
|
|
|
|
|
|
|
const localesMap = loadLocalesMap(modules); |
|
|
|
|
|
|
|
let loadMessages: LoadMessageFn; |
|
|
|
@ -75,6 +79,7 @@ async function loadLocaleMessages(lang: SupportedLanguagesType) { |
|
|
|
if (unref(i18n.global.locale) === lang) { |
|
|
|
return setI18nLanguage(lang); |
|
|
|
} |
|
|
|
setSimpleLocale(lang); |
|
|
|
|
|
|
|
const message = await localesMap[lang]?.(); |
|
|
|
|
|
|
|
|
|
|
|
@ -331,9 +331,6 @@ importers: |
|
|
|
'@vben/styles': |
|
|
|
specifier: workspace:* |
|
|
|
version: link:../packages/styles |
|
|
|
'@vueuse/core': |
|
|
|
specifier: ^11.0.3 |
|
|
|
version: 11.0.3(vue@3.4.38(typescript@5.5.4)) |
|
|
|
lucide-vue-next: |
|
|
|
specifier: ^0.436.0 |
|
|
|
version: 0.436.0(vue@3.4.38(typescript@5.5.4)) |
|
|
|
@ -350,9 +347,6 @@ importers: |
|
|
|
'@nolebase/vitepress-plugin-git-changelog': |
|
|
|
specifier: ^2.4.0 |
|
|
|
version: 2.4.0(@algolia/client-search@4.24.0)(@types/node@22.5.1)(async-validator@4.2.5)(axios@1.7.5)(nprogress@0.2.0)(postcss@8.4.41)(qrcode@1.5.4)(sass@1.77.8)(search-insights@2.16.3)(sortablejs@1.15.2)(terser@5.31.6)(typescript@5.5.4) |
|
|
|
'@types/markdown-it': |
|
|
|
specifier: ^14.1.2 |
|
|
|
version: 14.1.2 |
|
|
|
'@vben/vite-config': |
|
|
|
specifier: workspace:* |
|
|
|
version: link:../internal/vite-config |
|
|
|
@ -1093,6 +1087,9 @@ importers: |
|
|
|
'@intlify/core-base': |
|
|
|
specifier: ^9.14.0 |
|
|
|
version: 9.14.0 |
|
|
|
'@vben-core/composables': |
|
|
|
specifier: workspace:* |
|
|
|
version: link:../@core/composables |
|
|
|
vue: |
|
|
|
specifier: 3.4.38 |
|
|
|
version: 3.4.38(typescript@5.5.4) |
|
|
|
|