UNPKG

@ui18n/vue

Version:

💚 Vue 3 internationalization with Composition API, auto-reactive translations, and seamless TypeScript support

310 lines (304 loc) 9.43 kB
import { createUI18n, createErrorHandler } from '@ui18n/core'; export { UI18n, createUI18n } from '@ui18n/core'; import { reactive, readonly, inject, ref, watch, computed } from 'vue'; // 定义注入的 key const UI18nSymbol = Symbol('ui18n'); /** * 创建 UI18n Vue 插件 */ const createUI18nPlugin = (config) => { const ui18n = createUI18n(config); // 创建错误处理器(不传递fallbackManager,让ErrorHandler自行管理) const errorHandler = createErrorHandler({ enabled: true, logLevel: 'error', autoFix: true }); const state = reactive({ currentLanguage: ui18n.getCurrentLanguage(), }); const setLanguage = (language) => { ui18n.setLanguage(language); // 确保状态同步 state.currentLanguage = ui18n.getCurrentLanguage(); }; const t = (text, params, options) => { try { return ui18n.t(text, params, options); } catch (error) { console.warn('Translation failed:', error); return text; } }; const plugin = { install(app) { app.provide(UI18nSymbol, { ui18n, state: readonly(state), setLanguage, t, }); // 提供ErrorHandler实例 app.provide('ui18n-error-handler', errorHandler); // 将 t 方法挂载到全局属性,方便在模板中使用 app.config.globalProperties.$t = t; // 设置全局错误处理器 app.config.errorHandler = (error, instance, info) => { // 确保error是Error类型 if (!(error instanceof Error)) { return; } // 如果是UI18n相关错误,使用ErrorHandler处理 if (error.message.includes('ui18n') || error.message.includes('translation')) { errorHandler.handleError(error, { framework: 'vue' }).catch(console.error); } // 继续抛出错误,让其他错误处理器也能处理 console.error('Vue Error:', error, info); }; } }; return plugin; }; // 为了兼容性,提供别名导出 const UI18nPlugin = createUI18nPlugin; /** * useUI18n 组合式函数 * 用于在组件中方便地访问 ui18n 实例和相关功能 * @returns UI18n 上下文 */ const useUI18n = () => { const context = inject(UI18nSymbol); if (!context) { throw new Error('useUI18n must be used within a UI18n-installed app'); } return context; }; /** * Vue Composition API for language management * * @returns Language management utilities and reactive state * * @example * ```vue * <script setup> * import { useLanguage } from '@ui18n/vue'; * * const { * currentLanguage, * supportedLanguages, * switchLanguage, * getLanguageDisplayName * } = useLanguage(); * </script> * * <template> * <select :value="currentLanguage" @change="switchLanguage($event.target.value)"> * <option v-for="lang in supportedLanguages" :key="lang" :value="lang"> * {{ getLanguageDisplayName(lang) }} * </option> * </select> * </template> * ``` */ function useLanguage() { const ui18n = inject('ui18n'); const isChanging = ref(false); const error = ref(null); if (!ui18n) { console.warn('useLanguage: UI18n instance not found. Make sure to install the UI18n plugin.'); } // Reactive current language const currentLanguage = ref(ui18n?.getCurrentLanguage() || 'en'); // Watch for language changes watch(currentLanguage, (newLang) => { if (ui18n && newLang !== ui18n.getCurrentLanguage()) { ui18n.setLanguage(newLang); } }); /** * Get supported languages */ const supportedLanguages = computed(() => { return ui18n?.getSupportedLanguages() || []; }); /** * Switch to a new language */ const switchLanguage = async (language) => { if (!ui18n || language === currentLanguage.value) return; try { isChanging.value = true; error.value = null; ui18n.setLanguage(language); currentLanguage.value = language; // Preload translations for the new language if storage adapter is available if (ui18n.loadPersistedTranslation) { await ui18n.loadPersistedTranslation(language); } } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Language switch failed'; error.value = errorMessage; console.error('Language switch error:', err); } finally { isChanging.value = false; } }; /** * Get display name for a language */ const getLanguageDisplayName = (language) => { return ui18n?.getLanguageDisplayName?.(language) || language; }; /** * Detect language from text */ const detectLanguage = (text) => { return ui18n?.detectLanguage?.(text) || 'en'; }; /** * Check if a language is supported */ const isLanguageSupported = (language) => { const supported = supportedLanguages.value; return supported.includes(language); }; /** * Get recommended languages based on current language */ const getRecommendedLanguages = () => { const current = currentLanguage.value; const all = supportedLanguages.value; // Put current language first, then others return [current, ...all.filter((lang) => lang !== current)].slice(0, 5); }; /** * Get available language packs */ const getAvailableLanguages = async () => { if (!ui18n?.getAvailableLanguages) { return supportedLanguages.value; } try { return await ui18n.getAvailableLanguages(); } catch (err) { console.warn('Failed to get available languages:', err); return supportedLanguages.value; } }; return { // State currentLanguage: computed(() => currentLanguage.value), supportedLanguages, isChanging: computed(() => isChanging.value), error: computed(() => error.value), // Methods switchLanguage, getLanguageDisplayName, detectLanguage, isLanguageSupported, getRecommendedLanguages, getAvailableLanguages, // Utilities clearError: () => { error.value = null; } }; } /** * Vue Composition API for UI18n translation * * @returns Translation utilities and reactive state * * @example * ```vue * <script setup> * import { useTranslation } from '@ui18n/vue'; * * const { t, isLoading, error } = useTranslation(); * * const greeting = await t('Hello, World!'); * </script> * ``` */ function useTranslation() { const ui18n = inject('ui18n'); const isLoading = ref(false); const error = ref(null); if (!ui18n) { console.warn('useTranslation: UI18n instance not found. Make sure to install the UI18n plugin.'); } /** * Translate text */ const t = async (text, options) => { if (!ui18n) { console.warn('UI18n not available, returning original text'); return text; } try { isLoading.value = true; error.value = null; const result = await ui18n.t(text, options); return result; } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Translation failed'; error.value = errorMessage; console.error('Translation error:', err); return text; // fallback to original text } finally { isLoading.value = false; } }; /** * Get current language reactively */ const currentLanguage = computed(() => { return ui18n?.getCurrentLanguage() || 'en'; }); /** * Get supported languages */ const supportedLanguages = computed(() => { return ui18n?.getSupportedLanguages() || []; }); /** * Translate multiple keys at once */ const translateBatch = async (keys, options) => { if (!ui18n) return keys; try { isLoading.value = true; error.value = null; const results = await Promise.all(keys.map(key => ui18n.t(key, options))); return results; } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Batch translation failed'; error.value = errorMessage; console.error('Batch translation error:', err); return keys; // fallback to original keys } finally { isLoading.value = false; } }; return { t, translateBatch, currentLanguage, supportedLanguages, isLoading: computed(() => isLoading.value), error: computed(() => error.value), // Utility methods clearError: () => { error.value = null; } }; } export { UI18nPlugin, UI18nSymbol, createUI18nPlugin, useLanguage, useTranslation, useUI18n };