@ui18n/vue
Version:
💚 Vue 3 internationalization with Composition API, auto-reactive translations, and seamless TypeScript support
310 lines (304 loc) • 9.43 kB
JavaScript
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 };