Skip to content

Commit

Permalink
feat: generate Locale type based on configuration (#3025)
Browse files Browse the repository at this point in the history
* feat: generate `Locale` type based on configuration

* feat: use `Locale` type where relevant

* fix: use `ComposerGeneratedTypeConfig`

* fix: use `IntlifyGeneratedTypeConfig` interface for augmentation

* fix: rename augment interface

* chore(deps): update `vue-i18n` to `10.0.0-beta.4`
  • Loading branch information
BobbieGoede authored Jul 16, 2024
1 parent 5e26133 commit 9aa7171
Show file tree
Hide file tree
Showing 14 changed files with 101 additions and 91 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
"sucrase": "^3.35.0",
"ufo": "^1.3.1",
"unplugin": "^1.10.1",
"vue-i18n": "^10.0.0-beta.1",
"vue-i18n": "^10.0.0-beta.4",
"vue-router": "^4.4.0"
},
"devDependencies": {
Expand Down
73 changes: 32 additions & 41 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 15 additions & 5 deletions src/gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { genImport, genDynamicImport } from 'knitwork'
import { withQuery } from 'ufo'
import { resolve, relative, join } from 'pathe'
import { distDir, runtimeDir } from './dirs'
import { getLayerI18n, getLocalePaths, toCode } from './utils'
import { getLayerI18n, getLocalePaths, getNormalizedLocales, toCode } from './utils'

import type { Nuxt } from '@nuxt/schema'
import type { PrerenderTarget } from './utils'
import type { NuxtI18nOptions, LocaleInfo, VueI18nConfigPathInfo, FileMeta, LocaleObject } from './types'
import type { Locale } from 'vue-i18n'

export type LoaderOptions = {
vueI18nConfigPaths: Required<VueI18nConfigPathInfo>[]
Expand All @@ -31,7 +32,7 @@ const generateVueI18nConfiguration = (config: Required<VueI18nConfigPathInfo>, i
}

export function simplifyLocaleOptions(nuxt: Nuxt, options: NuxtI18nOptions) {
const isLocaleObjectsArray = (locales?: string[] | LocaleObject[]) => locales?.some(x => typeof x !== 'string')
const isLocaleObjectsArray = (locales?: Locale[] | LocaleObject[]) => locales?.some(x => typeof x !== 'string')

const hasLocaleObjects =
nuxt.options._layers.some(layer => isLocaleObjectsArray(getLayerI18n(layer)?.locales)) ||
Expand Down Expand Up @@ -64,7 +65,7 @@ export function generateLoaderOptions(
const importMapper = new Map<string, { key: string; load: string; cache: string }>()
const importStrings: string[] = []

function generateLocaleImports(locale: string, meta: NonNullable<LocaleInfo['meta']>[number], isServer = false) {
function generateLocaleImports(locale: Locale, meta: NonNullable<LocaleInfo['meta']>[number], isServer = false) {
if (importMapper.has(meta.key)) return
const importSpecifier = genImportSpecifier({ ...meta, isServer }, 'locale', { locale })
const importer = { code: locale, key: meta.loadPath, load: '', cache: meta.file.cache ?? true }
Expand Down Expand Up @@ -165,7 +166,8 @@ 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[]'
const resolvedLocaleType = typeof generatedLocales === 'string' ? 'Locale[]' : 'LocaleObject[]'
const localeCodeStrings = getNormalizedLocales(options.locales).map(x => x.code)

const i18nType = `${vueI18nTypes.join(' & ')} & NuxtI18nRoutingCustomProperties<${resolvedLocaleType}>`

Expand Down Expand Up @@ -197,13 +199,21 @@ declare module 'vue-i18n' {
interface VueI18n extends NuxtI18nRoutingCustomProperties<${resolvedLocaleType}> {}
}
declare module '@intlify/core' {
// generated based on configured locales
interface GeneratedTypeConfig {
locale: ${localeCodeStrings.map(x => JSON.stringify(x)).join(' | ')}
}
}
declare module '#app' {
interface NuxtApp {
$i18n: ${i18nType}
}
}
${options.experimental?.autoImportTranslationFunctions && globalTranslationTypes || ''}
${(options.experimental?.autoImportTranslationFunctions && globalTranslationTypes) || ''}
export {}`
}
18 changes: 11 additions & 7 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ import { applyLayerOptions, checkLayerOptions, resolveLayerVueI18nConfigInfo } f
import { generateTemplateNuxtI18nOptions } from './template'

import type { HookResult } from '@nuxt/schema'
import type { NuxtI18nOptions } from './types'
import type { LocaleObject, NuxtI18nOptions } from './types'
import type { Locale } from 'vue-i18n'

export * from './types'

Expand Down Expand Up @@ -362,8 +363,11 @@ export default defineNuxtModule<NuxtI18nOptions>({
}
})

// Prevent type errors while configuring locale codes, as generated types will conflict with changes
type UserNuxtI18nOptions = Omit<NuxtI18nOptions, 'locales'> & { locales?: string[] | LocaleObject<string>[] }

// Used by nuxt/module-builder for `types.d.ts` generation
export interface ModuleOptions extends NuxtI18nOptions {}
export interface ModuleOptions extends UserNuxtI18nOptions {}

export interface ModulePublicRuntimeConfig {
i18n: {
Expand Down Expand Up @@ -454,13 +458,13 @@ export interface ModuleRuntimeHooks {
// NOTE: To make type inference work the function signature returns `HookResult`
// Should return `string | void`
'i18n:beforeLocaleSwitch': <Context = unknown>(params: {
oldLocale: string
newLocale: string
oldLocale: Locale
newLocale: Locale
initialSetup: boolean
context: Context
}) => HookResult

'i18n:localeSwitched': (params: { oldLocale: string; newLocale: string }) => HookResult
'i18n:localeSwitched': (params: { oldLocale: Locale; newLocale: Locale }) => HookResult
}

// Used by module for type inference in source code
Expand All @@ -470,10 +474,10 @@ declare module '#app' {

declare module '@nuxt/schema' {
interface NuxtConfig {
['i18n']?: Partial<ModuleOptions>
['i18n']?: Partial<UserNuxtI18nOptions>
}
interface NuxtOptions {
['i18n']?: ModuleOptions
['i18n']?: UserNuxtI18nOptions
}
interface NuxtHooks extends ModuleHooks {}
interface PublicRuntimeConfig extends ModulePublicRuntimeConfig {}
Expand Down
10 changes: 5 additions & 5 deletions src/runtime/compatibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ export function getLocale(i18n: I18n): Locale {
return getI18nProperty(i18n, 'locale')
}

export function getLocales(i18n: I18n): string[] | LocaleObject[] {
export function getLocales(i18n: I18n): Locale[] | LocaleObject[] {
return getI18nProperty(i18n, 'locales')
}

export function getLocaleCodes(i18n: I18n): string[] {
export function getLocaleCodes(i18n: I18n): Locale[] {
return getI18nProperty(i18n, 'localeCodes')
}

Expand All @@ -93,15 +93,15 @@ export function mergeLocaleMessage(i18n: I18n, locale: Locale, messages: Record<

export async function onBeforeLanguageSwitch(
i18n: I18n,
oldLocale: string,
newLocale: string,
oldLocale: Locale,
newLocale: Locale,
initial: boolean,
context: NuxtApp
) {
return getI18nTarget(i18n).onBeforeLanguageSwitch(oldLocale, newLocale, initial, context)
}

export function onLanguageSwitched(i18n: I18n, oldLocale: string, newLocale: string) {
export function onLanguageSwitched(i18n: I18n, oldLocale: Locale, newLocale: Locale) {
return getI18nTarget(i18n).onLanguageSwitched(oldLocale, newLocale)
}

Expand Down
6 changes: 3 additions & 3 deletions src/runtime/components/NuxtLinkLocale.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useLocalePath } from '#i18n'
import { useLocalePath, type Locale } from '#i18n'
import { defineComponent, computed, h } from 'vue'
import { defineNuxtLink } from '#imports'
import { hasProtocol } from 'ufo'
Expand All @@ -8,13 +8,13 @@ import type { NuxtLinkProps } from 'nuxt/app'

const NuxtLinkLocale = defineNuxtLink({ componentName: 'NuxtLinkLocale' })

export default defineComponent<NuxtLinkProps & { locale?: string }>({
export default defineComponent<NuxtLinkProps & { locale?: Locale }>({
name: 'NuxtLinkLocale',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- FIXME
props: {
...NuxtLinkLocale.props,
locale: {
type: String as PropType<string>,
type: String as PropType<Locale>,
default: undefined,
required: false
}
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/components/SwitchLocalePathLink.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SWITCH_LOCALE_PATH_LINK_IDENTIFIER } from '#build/i18n.options.mjs'
import { useSwitchLocalePath } from '#i18n'
import { useSwitchLocalePath, type Locale } from '#i18n'
import { defineNuxtLink } from '#imports'
import { Comment, defineComponent, h } from 'vue'

Expand All @@ -11,7 +11,7 @@ export default defineComponent({
name: 'SwitchLocalePathLink',
props: {
locale: {
type: String as PropType<string>,
type: String as PropType<Locale>,
required: true
}
},
Expand Down
Loading

0 comments on commit 9aa7171

Please sign in to comment.