Skip to content
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

feat: generate Locale type based on configuration #3025

Merged
merged 6 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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