-
-
Notifications
You must be signed in to change notification settings - Fork 7.1k
refactor: migrate locale/RTL to @vuetify/v0 #22753
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Changes from all commits
a32eed3
d9fc3be
9968b9c
1a563ac
52f4e75
8c876c3
543c8ca
7d78e93
27e5b4b
4537b43
3c0849f
9707e4d
72150ee
6bd3c1c
00c88a5
bf11885
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,3 +26,6 @@ __screenshots__/ | |
|
|
||
| .vercel | ||
| .now | ||
|
|
||
| # Superpowers | ||
| docs/superpowers/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,71 +1,31 @@ | ||
| // Utilities | ||
| import { computed, inject, provide, ref, toRef } from 'vue' | ||
| import { createVuetifyAdapter } from '@/locale/adapters/vuetify' | ||
| import { createLocale as createV0Locale, createRtl as createV0Rtl, isFunction } from '@vuetify/v0' | ||
| import { computed, inject, provide, ref, shallowRef, toRef, watch } from 'vue' | ||
| import en from '@/locale/en' | ||
|
|
||
| // Types | ||
| import type { InjectionKey, Ref, ShallowRef } from 'vue' | ||
| import type { LocaleContext, RtlContext } from '@vuetify/v0' | ||
| import type { InjectionKey, Ref } from 'vue' | ||
|
|
||
| export interface LocaleMessages { | ||
| [key: string]: LocaleMessages | string | ||
| } | ||
|
|
||
| export interface LocaleOptions { | ||
| decimalSeparator?: string | ||
| messages?: LocaleMessages | ||
| messages?: Record<string, LocaleMessages> | ||
| locale?: string | ||
| fallback?: string | ||
| adapter?: LocaleInstance | ||
| } | ||
|
|
||
| export interface LocaleInstance { | ||
| name: string | ||
| decimalSeparator: ShallowRef<string> | ||
| messages: Ref<LocaleMessages> | ||
| current: Ref<string> | ||
| fallback: Ref<string> | ||
| t: (key: string, ...params: unknown[]) => string | ||
| n: (value: number) => string | ||
| provide: (props: LocaleOptions) => LocaleInstance | ||
| n: (value: number, options?: Intl.NumberFormatOptions) => string | ||
| decimalSeparator: Ref<string> | ||
| } | ||
|
|
||
| export const LocaleSymbol: InjectionKey<LocaleInstance & RtlInstance> = Symbol.for('vuetify:locale') | ||
|
|
||
| function isLocaleInstance (obj: any): obj is LocaleInstance { | ||
| return obj.name != null | ||
| } | ||
|
|
||
| export function createLocale (options?: LocaleOptions & RtlOptions) { | ||
| const i18n = options?.adapter && isLocaleInstance(options?.adapter) ? options?.adapter : createVuetifyAdapter(options) | ||
| const rtl = createRtl(i18n, options) | ||
|
|
||
| return { ...i18n, ...rtl } | ||
| } | ||
|
|
||
| export function useLocale () { | ||
| const locale = inject(LocaleSymbol) | ||
|
|
||
| if (!locale) throw new Error('[Vuetify] Could not find injected locale instance') | ||
|
|
||
| return locale | ||
| } | ||
|
|
||
| export function provideLocale (props: LocaleOptions & RtlProps) { | ||
| const locale = inject(LocaleSymbol) | ||
|
|
||
| if (!locale) throw new Error('[Vuetify] Could not find injected locale instance') | ||
|
|
||
| const i18n = locale.provide(props) | ||
| const rtl = provideRtl(i18n, locale.rtl, props) | ||
|
|
||
| const data = { ...i18n, ...rtl } | ||
|
|
||
| provide(LocaleSymbol, data) | ||
|
|
||
| return data | ||
| } | ||
|
|
||
| // RTL | ||
|
|
||
| export interface RtlOptions { | ||
| rtl?: Record<string, boolean> | ||
| } | ||
|
|
@@ -80,8 +40,19 @@ export interface RtlInstance { | |
| rtlClasses: Ref<string> | ||
| } | ||
|
|
||
| /** @internal */ | ||
| export interface InternalLocaleData { | ||
| _messages: Record<string, LocaleMessages> | ||
| _fallback: string | ||
| } | ||
|
|
||
| export interface FullLocaleInstance extends LocaleInstance, RtlInstance, InternalLocaleData {} | ||
|
|
||
| export const LocaleSymbol: InjectionKey<FullLocaleInstance> = Symbol.for('vuetify:locale') | ||
| export const RtlSymbol: InjectionKey<RtlInstance> = Symbol.for('vuetify:rtl') | ||
|
|
||
| const LANG_PREFIX = '$vuetify.' | ||
|
|
||
| function genDefaults () { | ||
| return { | ||
| af: false, | ||
|
|
@@ -129,18 +100,144 @@ function genDefaults () { | |
| } | ||
| } | ||
|
|
||
| export function createRtl (i18n: LocaleInstance, options?: RtlOptions): RtlInstance { | ||
| function createLocaleInstance ( | ||
| v0Locale: LocaleContext, | ||
| options?: { decimalSeparator?: string } | ||
| ): LocaleInstance { | ||
| const current = computed<string>({ | ||
| get: () => String(v0Locale.selectedId.value ?? 'en'), | ||
| set: v => v0Locale.select(v), | ||
| }) | ||
|
|
||
| function t (key: string, ...params: unknown[]): string { | ||
| const stripped = key.startsWith(LANG_PREFIX) ? key.slice(LANG_PREFIX.length) : key | ||
| return v0Locale.t(stripped, ...params) | ||
| } | ||
|
|
||
| function n (value: number, options?: Intl.NumberFormatOptions): string { | ||
| if (options) { | ||
| return new Intl.NumberFormat([current.value], options).format(value) | ||
| } | ||
| return v0Locale.n(value) | ||
| } | ||
|
|
||
| const decimalSeparator = toRef(() => { | ||
| if (options?.decimalSeparator) return options.decimalSeparator | ||
| const formatted = n(0.1) | ||
| return formatted.includes(',') ? ',' : '.' | ||
| }) | ||
|
|
||
| return { | ||
| current, | ||
| t, | ||
| n, | ||
| decimalSeparator, | ||
| } | ||
| } | ||
|
|
||
| function createRtlInstance ( | ||
| locale: LocaleInstance, | ||
| rtlMap: Ref<Record<string, boolean>>, | ||
| v0Rtl: RtlContext | ||
| ): RtlInstance { | ||
| const isRtl = shallowRef(v0Rtl.isRtl.value) | ||
|
|
||
| watch(() => locale.current.value, current => { | ||
| const value = rtlMap.value[current] ?? false | ||
| v0Rtl.isRtl.value = value | ||
| isRtl.value = value | ||
| }, { immediate: true }) | ||
|
|
||
| return { | ||
| isRtl, | ||
| rtl: rtlMap, | ||
| rtlClasses: toRef(() => `v-locale--is-${isRtl.value ? 'rtl' : 'ltr'}`), | ||
| } | ||
| } | ||
|
|
||
| export function createLocale (options?: LocaleOptions & RtlOptions) { | ||
| if (options?.adapter) { | ||
| const rtl = createRtlFromAdapter(options.adapter, options) | ||
| return { ...options.adapter, ...rtl } | ||
| } | ||
|
|
||
| const messages = { en, ...options?.messages } | ||
| const defaultLocale = options?.locale ?? 'en' | ||
| const fallback = options?.fallback ?? 'en' | ||
|
|
||
| const v0Locale = createV0Locale({ | ||
| default: defaultLocale, | ||
| fallback, | ||
| messages, | ||
| }) | ||
|
|
||
| const v0Rtl = createV0Rtl({ | ||
| default: false, | ||
| target: null, | ||
| }) | ||
|
|
||
| const rtlMap = ref<Record<string, boolean>>(options?.rtl ?? genDefaults()) | ||
| const locale = createLocaleInstance(v0Locale, options) | ||
| const rtl = createRtlInstance(locale, rtlMap, v0Rtl) | ||
|
|
||
| return { ...locale, ...rtl, _messages: messages, _fallback: fallback } satisfies FullLocaleInstance | ||
| } | ||
|
|
||
| function createRtlFromAdapter (adapter: LocaleInstance, options?: RtlOptions): RtlInstance { | ||
| const rtl = ref<Record<string, boolean>>(options?.rtl ?? genDefaults()) | ||
| const isRtl = computed(() => rtl.value[i18n.current.value] ?? false) | ||
| const isRtl = computed(() => rtl.value[adapter.current.value] ?? false) | ||
|
|
||
| return { | ||
| isRtl, | ||
| rtl, | ||
| rtlClasses: toRef(() => `v-locale--is-${isRtl.value ? 'rtl' : 'ltr'}`), | ||
| } satisfies RtlInstance | ||
| } | ||
|
|
||
| export function useLocale () { | ||
| const locale = inject(LocaleSymbol) | ||
|
|
||
| if (!locale) throw new Error('[Vuetify] Could not find injected locale instance') | ||
|
|
||
| return locale | ||
| } | ||
|
|
||
| export function provideLocale (props: LocaleOptions & RtlProps) { | ||
| const parent = inject(LocaleSymbol) | ||
|
|
||
| if (!parent) throw new Error('[Vuetify] Could not find injected locale instance') | ||
|
|
||
| if ('provide' in parent && isFunction(parent.provide)) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Especially when you're still using provide. |
||
| const i18n = parent.provide(props) | ||
| const rtl = provideRtl(i18n, parent.rtl, props) | ||
| const data = { ...i18n, ...rtl } | ||
| provide(LocaleSymbol, data) | ||
| return data | ||
| } | ||
|
|
||
| const parentData = parent | ||
| const parentMessages = parentData._messages ?? {} | ||
| const parentFallback = parentData._fallback ?? 'en' | ||
| const messages = props.messages | ||
| ? { ...parentMessages, [props.locale ?? parent.current.value]: props.messages } | ||
| : parentMessages | ||
| const fallback = props.fallback ?? parentFallback | ||
|
|
||
| const v0Locale = createV0Locale({ | ||
| default: props.locale ?? parent.current.value, | ||
| fallback, | ||
| messages, | ||
| }) | ||
|
|
||
| const locale = createLocaleInstance(v0Locale, props) | ||
| const rtl = provideRtl(locale, parent.rtl, props) | ||
|
|
||
| const data = { ...locale, ...rtl, _messages: messages, _fallback: fallback } satisfies FullLocaleInstance | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any particular reason to "remove" messages and fallback if they're just being renamed and made private? |
||
| provide(LocaleSymbol, data) | ||
| return data | ||
| } | ||
|
|
||
| export function provideRtl (locale: LocaleInstance, rtl: RtlInstance['rtl'], props: RtlProps): RtlInstance { | ||
| function provideRtl (locale: LocaleInstance, rtl: RtlInstance['rtl'], props: RtlProps): RtlInstance { | ||
| const isRtl = computed(() => props.rtl ?? rtl.value[locale.current.value] ?? false) | ||
|
|
||
| return { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,13 @@ import type { Ref } from 'vue' | |
| import type { I18n, useI18n } from 'vue-i18n' | ||
| import type { LocaleInstance, LocaleMessages, LocaleOptions } from '@/composables/locale' | ||
|
|
||
| export interface VueI18nLocaleInstance extends LocaleInstance { | ||
| name: string | ||
| fallback: Ref<string> | ||
| messages: Ref<LocaleMessages> | ||
| provide: (props: LocaleOptions) => VueI18nLocaleInstance | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why were name and provide removed on LocaleInstance but not here? |
||
| } | ||
|
|
||
| type VueI18nAdapterParams = { | ||
| i18n: I18n<any, {}, {}, string, false> | ||
| useI18n: typeof useI18n | ||
|
|
@@ -38,7 +45,7 @@ function createProvideFunction (data: { | |
| messages: Ref<LocaleMessages> | ||
| useI18n: typeof useI18n | ||
| }) { | ||
| return (props: LocaleOptions): LocaleInstance => { | ||
| return (props: LocaleOptions): VueI18nLocaleInstance => { | ||
| const current = useProvided(props, 'locale', data.current) | ||
| const fallback = useProvided(props, 'fallback', data.fallback) | ||
| const messages = useProvided(props, 'messages', data.messages) | ||
|
|
@@ -69,7 +76,7 @@ function createProvideFunction (data: { | |
| } | ||
| } | ||
|
|
||
| export function createVueI18nAdapter ({ i18n, useI18n }: VueI18nAdapterParams): LocaleInstance { | ||
| export function createVueI18nAdapter ({ i18n, useI18n }: VueI18nAdapterParams): VueI18nLocaleInstance { | ||
| const current = i18n.global.locale | ||
| const fallback = i18n.global.fallbackLocale as Ref<any> | ||
| const messages = i18n.global.messages | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No idea if anyone actually did this but it used to be possible to add to messages at runtime, now they all have to be declared upfront.