import type { Composer, I18n } from 'vue-i18n' import { createI18n as createVueI18n } from 'vue-i18n' import en from './locales/en.json' import es from './locales/es.json' import fr from './locales/fr.json' import he from './locales/he.json' import it from './locales/it.json' import ru from './locales/ru.json' export type MessageSchema = typeof en // Bagelink's built-in locales const bagelinkLocales = { en, he, it, es, fr, ru, } as const export interface CreateBagelI18nOptions { /** Active locale (default: 'en'). Same as vue-i18n's `locale`. */ locale?: string /** Fallback locale (default: 'en'). Same as vue-i18n's `fallbackLocale`. */ fallbackLocale?: string /** User's custom messages merged with bagelink's built-in translations. Same as vue-i18n's `messages`. */ messages?: Record> } let i18nInstance: I18n | null = null /** * Creates an i18n instance with bagelink's translations merged with user's. * Options mirror vue-i18n's `createI18n` for consistency. */ export function createI18n(options: CreateBagelI18nOptions = {}): I18n { const { locale = 'en', fallbackLocale = 'en', messages = {}, } = options // Merge bagelink locales with user messages for all supported locales const supportedLocales = Object.keys(bagelinkLocales) const mergedMessages: Record> = {} for (const lang of supportedLocales) { mergedMessages[lang] = { ...(bagelinkLocales[lang as keyof typeof bagelinkLocales] || {}), ...(messages[lang] || {}), } } // Also include any user-provided locales not in bagelink's built-ins for (const lang of Object.keys(messages)) { if (!mergedMessages[lang]) { mergedMessages[lang] = messages[lang] } } i18nInstance = createVueI18n({ locale, fallbackLocale, messages: mergedMessages as any, legacy: false, globalInjection: true, }) return i18nInstance } /** * Get the global i18n instance */ export function getI18n(): I18n { if (!i18nInstance) { throw new Error('i18n not initialized. Call createI18n() first.') } return i18nInstance } interface UseI18nReturn { $t: Composer['t'] t: Composer['t'] locale: Composer['locale'] availableLocales: Composer['availableLocales'] fallbackLocale: Composer['fallbackLocale'] n: Composer['n'] d: Composer['d'] } /** * useI18n wrapper that returns bagelink's typed i18n functions. * Uses the global i18n instance directly to work in all contexts, * including dialog containers mounted in a secondary app instance. */ export function useI18n(): UseI18nReturn { const global = getI18n().global as Composer return { $t: global.t, t: global.t, locale: global.locale, availableLocales: global.availableLocales, fallbackLocale: global.fallbackLocale, n: global.n, d: global.d, } } // Export locale data for direct access if needed export { bagelinkLocales } /** * A string that is either a plain string or a translation key prefixed with `$t:`. * @example * 'Save' // plain string, used as-is * '$t:btn.save' // translation key, resolved via i18n */ export type TranslatableString = string | `$t:${string}` /** * Resolves a TranslatableString. * If the value starts with `$t:`, it's treated as a translation key. * Otherwise the string is returned as-is. * Works outside component context (uses the global i18n instance). * * @example * resolveI18n('Save') // → 'Save' * resolveI18n('$t:btn.save') // → 'Salva' (if locale is 'it') * resolveI18n(undefined) // → undefined */ export function resolveI18n(value: TranslatableString | undefined): string | undefined { if (value == null) return undefined if (!value.startsWith('$t:')) return value return (getI18n().global.t as (key: string) => string)(value.slice(3)) }