Skip to content

Commit

Permalink
feat: locales generated type narrowing (#2722)
Browse files Browse the repository at this point in the history
* refactor: options passing

* feat: narrow `locales` based on generated options

* chore: remove type assertions in playground
  • Loading branch information
BobbieGoede authored Jan 22, 2024
1 parent b396462 commit 489f13d
Show file tree
Hide file tree
Showing 12 changed files with 103 additions and 106 deletions.
4 changes: 1 addition & 3 deletions playground/pages/[...catch].vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
<script setup lang="ts">
import type { LocaleObject } from '#i18n'
const route = useRoute()
const { locale, locales } = useI18n()
const availableLocales = computed(() => {
return (locales.value as LocaleObject[]).filter(i => i.code !== locale.value)
return locales.value.filter(i => i.code !== locale.value)
})
definePageMeta({
Expand Down
6 changes: 2 additions & 4 deletions playground/pages/about/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script lang="ts">
import { defineComponent } from 'vue'
import type { ExportedGlobalComposer, LocaleObject } from '#i18n'
export default defineComponent({
mounted() {
Expand All @@ -12,11 +11,10 @@ export default defineComponent({
},
computed: {
availableLocales() {
return (this.$i18n.locales as LocaleObject[]).filter(i => i.code !== this.$i18n.locale)
return this.$i18n.locales.filter(i => i.code !== this.$i18n.locale)
},
switchableLocale() {
const i18n = this.$i18n as ExportedGlobalComposer
const _locales = (i18n.locales as LocaleObject[]).filter(i => i.code !== this.$i18n.locale)
const _locales = this.$i18n.locales.filter(i => i.code !== this.$i18n.locale)
return _locales.length !== 0 ? _locales[0] : { code: 'ja', name: '日本語' }
}
},
Expand Down
4 changes: 1 addition & 3 deletions playground/pages/category/[id].vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
<script setup lang="ts">
import type { LocaleObject } from '#i18n'
const route = useRoute()
const { locale, locales } = useI18n()
const availableLocales = computed(() => {
return (locales.value as LocaleObject[]).filter(i => i.code !== locale.value)
return locales.value.filter(i => i.code !== locale.value)
})
definePageMeta({
Expand Down
7 changes: 2 additions & 5 deletions playground/pages/index.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
<script setup lang="ts">
import { computed } from 'vue'
// import { useLocalePath, useSwitchLocalePath, useLocaleHead, useBrowserLocale } from '#i18n'
import type { LocaleObject } from '#i18n'
const route = useRoute()
const {
t,
Expand Down Expand Up @@ -34,12 +31,12 @@ console.log('message if github layer merged:', t('layer-test-key'))
console.log('experimental module', t('goodDay'))
function getLocaleName(code: string) {
const locale = (locales.value as LocaleObject[]).find(i => i.code === code)
const locale = locales.value.find(i => i.code === code)
return locale ? locale.name : code
}
const availableLocales = computed(() => {
return (locales.value as LocaleObject[]).filter(i => i.code !== locale.value)
return locales.value.filter(i => i.code !== locale.value)
})
const i = tm('items')
Expand Down
39 changes: 39 additions & 0 deletions src/gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import createDebug from 'debug'
import { EXECUTABLE_EXTENSIONS } from './constants'
import { genImport, genDynamicImport } from 'knitwork'
import { withQuery } from 'ufo'
import { resolve } from 'pathe'
import { distDir, runtimeDir } from './dirs'
import { getLayerI18n, getLocalePaths, toCode } from './utils'

import type { Nuxt } from '@nuxt/schema'
Expand Down Expand Up @@ -151,4 +153,41 @@ function genImportSpecifier(
return getLoadPath()
}

export function generateI18nPageTypes() {
return `// Generated by @nuxtjs/i18n
declare module 'nuxt/dist/pages/runtime' {
interface PageMeta {
nuxtI18n?: Record<string, any>
}
}
export {}`
}

export function generateI18nTypes(nuxt: Nuxt, options: NuxtI18nOptions) {
const vueI18nTypes = options.types === 'legacy' ? ['VueI18n'] : ['ExportedGlobalComposer', 'Composer']
const generatedLocales = simplifyLocaleOptions(nuxt, options)
const resolvedLocaleType = typeof generatedLocales === 'string' ? 'string[]' : 'LocaleObject[]'

// prettier-ignore
return `// Generated by @nuxtjs/i18n
import type { ${vueI18nTypes.join(', ')} } from 'vue-i18n'
import type { NuxtI18nRoutingCustomProperties, ComposerCustomProperties } from '${resolve(runtimeDir, 'types.ts')}'
import type { Strategies, Directions, LocaleObject } from '${resolve(distDir, 'types.d.ts')}'
declare module 'vue-i18n' {
interface ComposerCustom extends ComposerCustomProperties<${resolvedLocaleType}> {}
interface ExportedGlobalComposer extends NuxtI18nRoutingCustomProperties<${resolvedLocaleType}> {}
interface VueI18n extends NuxtI18nRoutingCustomProperties<${resolvedLocaleType}> {}
}
declare module '#app' {
interface NuxtApp {
$i18n: ${vueI18nTypes.join(' & ')} & NuxtI18nRoutingCustomProperties<${resolvedLocaleType}>
}
}
export {}`
}

/* eslint-enable @typescript-eslint/no-explicit-any */
16 changes: 16 additions & 0 deletions src/internal-global-types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Composer, ExportedGlobalComposer, VueI18n } from 'vue-i18n'
import type { ComposerCustomProperties, NuxtI18nRoutingCustomProperties } from './runtime/types'

declare module 'vue-i18n' {
interface ComposerCustom extends ComposerCustomProperties {}
interface ExportedGlobalComposer extends NuxtI18nRoutingCustomProperties {}
interface VueI18n extends NuxtI18nRoutingCustomProperties {}
}

declare module '#app' {
interface NuxtApp {
$i18n: VueI18n & ExportedGlobalComposer & Composer & NuxtI18nRoutingCustomProperties & I18nRoutingCustomProperties
}
}

export {}
27 changes: 13 additions & 14 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
addComponent,
addPlugin,
addTemplate,
addTypeTemplate,
addImports,
useLogger
} from '@nuxt/kit'
Expand All @@ -16,7 +17,7 @@ import { setupAlias } from './alias'
import { setupPages } from './pages'
import { setupNitro } from './nitro'
import { extendBundler } from './bundler'
import { generateLoaderOptions } from './gen'
import { generateI18nPageTypes, generateI18nTypes, generateLoaderOptions } from './gen'
import {
NUXT_I18N_MODULE_ID,
DEFAULT_OPTIONS,
Expand Down Expand Up @@ -228,25 +229,23 @@ export default defineNuxtModule<NuxtI18nOptions>({
})

/**
* To be plugged for `PageMeta` type definition on `NuxtApp`
* `PageMeta` augmentation to add `nuxtI18n` property
* TODO: Remove in v8.1, `useSetI18nParams` should be used instead
*/

if (!!options.dynamicRouteParams) {
addPlugin(resolve(runtimeDir, 'plugins/meta'))
addTypeTemplate({
filename: 'types/i18n-page-meta.d.ts',
getContents: () => generateI18nPageTypes()
})
}

/**
* add extend type definition
* `$i18n` type narrowing based on 'legacy' or 'composition'
* `locales` type narrowing based on generated configuration
*/

const isLegacyMode = () => options.types === 'legacy'

// To be plugged for `$i18n` type definition on `NuxtApp`
addPlugin(resolve(runtimeDir, isLegacyMode() ? 'plugins/legacy' : 'plugins/composition'))

nuxt.hook('prepare:types', ({ references }) => {
const vueI18nTypeFilename = resolve(runtimeDir, 'types')
references.push({ path: resolve(nuxt.options.buildDir, vueI18nTypeFilename) })
addTypeTemplate({
filename: 'types/i18n-plugin.d.ts',
getContents: () => generateI18nTypes(nuxt, i18nOptions)
})

/**
Expand Down
15 changes: 0 additions & 15 deletions src/runtime/plugins/composition.ts

This file was deleted.

15 changes: 0 additions & 15 deletions src/runtime/plugins/legacy.ts

This file was deleted.

12 changes: 0 additions & 12 deletions src/runtime/plugins/meta.ts

This file was deleted.

57 changes: 24 additions & 33 deletions src/runtime/types.d.ts → src/runtime/types.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,7 @@
import type { NuxtApp } from '#app'
import type { ComputedRef } from 'vue'
import type { Strategies, Directions, LocaleObject } from './routing/types'

export interface I18nRoutingCustomProperties {
/**
* List of locales
*
* @remarks
* Can either be an array of string codes (e.g. `['en', 'fr']`) or an array of {@link LocaleObject} for more complex configurations
*/
readonly locales: string[] | LocaleObject[]
/**
* List of locale codes
*/
readonly localeCodes: string[]
/**
* Base URL that is used in generating canonical links
*/
baseUrl: string
}
import type { Directions, LocaleObject, Strategies } from '../types'
import type { Locale } from 'vue-i18n'

/**
* Called before the app's locale is switched.
Expand Down Expand Up @@ -48,14 +31,16 @@ type BeforeLanguageSwitchHandler = (
*/
type LanguageSwitchedHandler = (oldLocale: string, newLocale: string) => Promise<void>

export interface ComposerCustomProperties {
export interface ComposerCustomProperties<
ConfiguredLocaleType extends string[] | LocaleObject[] = string[] | LocaleObject[]
> {
/**
* List of locales
*
* @remarks
* Can either be an array of string codes (e.g. `['en', 'fr']`) or an array of {@link LocaleObject} for more complex configurations
*/
locales: ComputedRef<string[] | LocaleObject[]>
locales: ComputedRef<ConfiguredLocaleType>
/**
* List of locale codes
*/
Expand Down Expand Up @@ -147,7 +132,24 @@ export interface ComposerCustomProperties {
waitForPendingLocaleChange: () => Promise<void>
}

export interface NuxtI18nRoutingCustomProperties {
export interface NuxtI18nRoutingCustomProperties<
ConfiguredLocaleType extends string[] | LocaleObject[] = string[] | LocaleObject[]
> {
/**
* List of locales
*
* @remarks
* Can either be an array of string codes (e.g. `['en', 'fr']`) or an array of {@link LocaleObject} for more complex configurations
*/
readonly locales: ConfiguredLocaleType
/**
* List of locale codes
*/
readonly localeCodes: string[]
/**
* Base URL that is used in generating canonical links
*/
baseUrl: string
/**
* Routing strategy.
*/
Expand Down Expand Up @@ -232,20 +234,9 @@ export interface NuxtI18nRoutingCustomProperties {
}

declare module 'vue-i18n' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ComposerCustom extends ComposerCustomProperties {}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ExportedGlobalComposer extends NuxtI18nRoutingCustomProperties, I18nRoutingCustomProperties {}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface VueI18n extends NuxtI18nRoutingCustomProperties, I18nRoutingCustomProperties {}

interface I18n {
__pendingLocale?: string
__pendingLocalePromise?: Promise<void>
__resolvePendingLocalePromise?: (value: void | PromiseLike<void>) => void
}
}

export {}
7 changes: 5 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,10 @@ export type LocalizeRoutesPrefixable = (options: PrefixLocalizedRouteOptions) =>
*
* @public
*/
export type I18nRoutingOptions<Context = unknown> = {
export type I18nRoutingOptions<
Context = unknown,
ConfiguredLocaleType extends string[] | LocaleObject[] = string[] | LocaleObject[]
> = {
/**
* Vue Router version
*
Expand Down Expand Up @@ -290,7 +293,7 @@ export type I18nRoutingOptions<Context = unknown> = {
*
* @defaultValue []
*/
locales?: string[] | LocaleObject[]
locales?: ConfiguredLocaleType
/**
* Routes strategy
*
Expand Down

0 comments on commit 489f13d

Please sign in to comment.