useAppI18n.ts 2.21 KB
import en from '~/locales/en'
import zhCN from '~/locales/zh-CN'

type Locale = 'en' | 'zh-CN'
type MessageValue = string | Record<string, unknown> | unknown[]

const DEFAULT_LOCALE: Locale = 'zh-CN'
const messages = {
  en,
  'zh-CN': zhCN
} as const

function resolveLocale(input?: string | null): Locale {
  if (input === 'zh' || input === 'zh-CN') {
    return 'zh-CN'
  }

  if (input === 'en') {
    return 'en'
  }

  return DEFAULT_LOCALE
}

function getValueByPath(source: Record<string, unknown>, path: string): MessageValue | undefined {
  return path.split('.').reduce<MessageValue | undefined>((acc, segment) => {
    if (!acc || typeof acc !== 'object' || Array.isArray(acc)) {
      return undefined
    }

    return (acc as Record<string, unknown>)[segment] as MessageValue | undefined
  }, source)
}

function interpolate(template: string, params?: Record<string, string | number>): string {
  if (!params) {
    return template
  }

  return template.replace(/\{(\w+)\}/g, (_, key: string) => {
    return params[key] === undefined ? `{${key}}` : String(params[key])
  })
}

export function useAppI18n() {
  const localeCookie = useCookie<Locale>('locale', {
    default: () => DEFAULT_LOCALE
  })

  const locale = useState<Locale>('app-locale', () => resolveLocale(localeCookie.value))

  watch(locale, (value) => {
    localeCookie.value = value
  }, { immediate: true })

  const setLocale = (value: Locale) => {
    locale.value = value
  }

  const t = (path: string, params?: Record<string, string | number>): string => {
    const current = getValueByPath(messages[locale.value] as unknown as Record<string, unknown>, path)
    const fallback = getValueByPath(messages.en as unknown as Record<string, unknown>, path)
    const value = current ?? fallback

    if (typeof value !== 'string') {
      return path
    }

    return interpolate(value, params)
  }

  const tm = <T = unknown>(path: string): T => {
    const current = getValueByPath(messages[locale.value] as unknown as Record<string, unknown>, path)
    const fallback = getValueByPath(messages.en as unknown as Record<string, unknown>, path)

    return (current ?? fallback) as T
  }

  return {
    locale,
    setLocale,
    t,
    tm
  }
}

export type { Locale }