UNPKG

@ui18n/angular

Version:

🅰️ Modern Angular internationalization with standalone components, signals, and dependency injection support for Angular 15+

1,722 lines (1,605 loc) 81.3 kB
import { Injectable, Directive, Input, Output, EventEmitter, Pipe, Component, ChangeDetectionStrategy, NgModule } from '@angular/core'; import { Subject, BehaviorSubject, combineLatest, interval } from 'rxjs'; import { map, distinctUntilChanged, debounceTime, takeUntil, startWith } from 'rxjs/operators'; import { createUI18n } from '@ui18n/core'; export { AIProvider, AIProviderConfig, AITranslator, BuiltinDictionary, CacheConfig, CacheManager, DEFAULT_CONFIG, DictionaryEntry, EnterpriseUI18nConfig, LanguageDetectionResult, LanguageDetector, ServiceMode, ServiceModeManager, SupportedLanguage, SystemStatus, TranslationPipeline, TranslationRequest, TranslationResult, TranslationSearchResult, TranslationStats, UI18n, UI18nConfig, VERSION, VersionControlManager, checkBrowserSupport, createDevelopmentUI18n, createEnterpriseUI18n, createLanguageSwitcher, createProductionUI18n, createTranslationMonitor, createUI18n, getRecommendedLanguages, quickSetup } from '@ui18n/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; } function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return (String )(t); } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _applyDecs2305(e, t, r, n, o, a) { function i(e, t, r) { return function (n, o) { return r && r(n), e[t].call(n, o); }; } function c(e, t) { for (var r = 0; r < e.length; r++) e[r].call(t); return t; } function s(e, t, r, n) { if ("function" != typeof e && (n || void 0 !== e)) throw new TypeError(t + " must " + (r || "be") + " a function" + (n ? "" : " or undefined")); return e; } function applyDec(e, t, r, n, o, a, c, u, l, f, p, d, h) { function m(e) { if (!h(e)) throw new TypeError("Attempted to access private element on non-instance"); } var y, v = t[0], g = t[3], b = !u; if (!b) { r || Array.isArray(v) || (v = [v]); var w = {}, S = [], A = 3 === o ? "get" : 4 === o || d ? "set" : "value"; f ? (p || d ? w = { get: _setFunctionName(function () { return g(this); }, n, "get"), set: function (e) { t[4](this, e); } } : w[A] = g, p || _setFunctionName(w[A], n, 2 === o ? "" : A)) : p || (w = Object.getOwnPropertyDescriptor(e, n)); } for (var P = e, j = v.length - 1; j >= 0; j -= r ? 2 : 1) { var D = v[j], E = r ? v[j - 1] : void 0, I = {}, O = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: n, metadata: a, addInitializer: function (e, t) { if (e.v) throw Error("attempted to call addInitializer after decoration was finished"); s(t, "An initializer", "be", true), c.push(t); }.bind(null, I) }; try { if (b) (y = s(D.call(E, P, O), "class decorators", "return")) && (P = y);else { var k, F; O.static = l, O.private = f, f ? 2 === o ? k = function (e) { return m(e), w.value; } : (o < 4 && (k = i(w, "get", m)), 3 !== o && (F = i(w, "set", m))) : (k = function (e) { return e[n]; }, (o < 2 || 4 === o) && (F = function (e, t) { e[n] = t; })); var N = O.access = { has: f ? h.bind() : function (e) { return n in e; } }; if (k && (N.get = k), F && (N.set = F), P = D.call(E, d ? { get: w.get, set: w.set } : w[A], O), d) { if ("object" == typeof P && P) (y = s(P.get, "accessor.get")) && (w.get = y), (y = s(P.set, "accessor.set")) && (w.set = y), (y = s(P.init, "accessor.init")) && S.push(y);else if (void 0 !== P) throw new TypeError("accessor decorators must return an object with get, set, or init properties or void 0"); } else s(P, (p ? "field" : "method") + " decorators", "return") && (p ? S.push(P) : w[A] = P); } } finally { I.v = true; } } return (p || d) && u.push(function (e, t) { for (var r = S.length - 1; r >= 0; r--) t = S[r].call(e, t); return t; }), p || b || (f ? d ? u.push(i(w, "get"), i(w, "set")) : u.push(2 === o ? w[A] : i.call.bind(w[A])) : Object.defineProperty(e, n, w)), P; } function u(e, t) { return Object.defineProperty(e, Symbol.metadata || Symbol.for("Symbol.metadata"), { configurable: true, enumerable: true, value: t }); } if (arguments.length >= 6) var l = a[Symbol.metadata || Symbol.for("Symbol.metadata")]; var f = Object.create(null == l ? null : l), p = function (e, t, r, n) { var o, a, i = [], s = function (t) { return _checkInRHS(t) === e; }, u = new Map(); function l(e) { e && i.push(c.bind(null, e)); } for (var f = 0; f < t.length; f++) { var p = t[f]; if (Array.isArray(p)) { var d = p[1], h = p[2], m = p.length > 3, y = 16 & d, v = !!(8 & d), g = 0 == (d &= 7), b = h + "/" + v; if (!g && !m) { var w = u.get(b); if (true === w || 3 === w && 4 !== d || 4 === w && 3 !== d) throw Error("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: " + h); u.set(b, !(d > 2) || d); } applyDec(v ? e : e.prototype, p, y, m ? "#" + h : _toPropertyKey(h), d, n, v ? a = a || [] : o = o || [], i, v, m, g, 1 === d, v && m ? s : r); } } return l(o), l(a), i; }(e, t, o, f); return r.length || u(e, f), { e: p, get c() { var t = []; return r.length && [u(applyDec(e, [r], n, e.name, 5, f, t), f), c.bind(null, t, e)]; } }; } var _UI18nService2; let _initClass$9, _classDecs$9; /** * 批量翻译选项 */ /** * 实时同步选项 */ /** * 缓存优化选项 */ /** * 实时同步状态 */ /** * 缓存统计信息 */ _classDecs$9 = [Injectable({ providedIn: 'root' })]; let _UI18nService; /** * Angular UI18n服务 * 提供响应式的翻译功能和状态管理 */ class UI18nService { constructor() { this.ui18nInstance = null; this.destroy$ = new Subject(); // 响应式状态 this.currentLanguageSubject = new BehaviorSubject('zh-CN'); this.isInitializedSubject = new BehaviorSubject(false); this.isLoadingSubject = new BehaviorSubject(false); this.statsSubject = new BehaviorSubject(null); this.statusSubject = new BehaviorSubject(null); // 高级功能状态 this.batchProgressSubject = new BehaviorSubject(0); this.realTimeSyncStatusSubject = new BehaviorSubject({ enabled: false, connected: false, method: 'polling', syncErrors: 0 }); this.cacheStatsSubject = new BehaviorSubject(null); this.syncErrorsSubject = new BehaviorSubject(0); // 公共Observable this.currentLanguage$ = this.currentLanguageSubject.asObservable(); this.isInitialized$ = this.isInitializedSubject.asObservable(); this.isLoading$ = this.isLoadingSubject.asObservable(); this.stats$ = this.statsSubject.asObservable(); this.status$ = this.statusSubject.asObservable(); // 高级功能Observable this.batchProgress$ = this.batchProgressSubject.asObservable(); this.realTimeSyncStatus$ = this.realTimeSyncStatusSubject.asObservable(); this.cacheStats$ = this.cacheStatsSubject.asObservable(); this.syncErrors$ = this.syncErrorsSubject.asObservable(); // 组合Observable this.isConnected$ = this.realTimeSyncStatus$.pipe(map(status => status.connected), distinctUntilChanged()); this.cacheHealthy$ = this.cacheStats$.pipe(map(stats => stats ? stats.memoryUtilization < 0.8 : true), distinctUntilChanged()); } /** * 初始化UI18n实例(基础版本) */ async initialize(config) { try { this.isLoadingSubject.next(true); this.ui18nInstance = createUI18n(config); // 设置当前语言 if (config.defaultLanguage) { this.currentLanguageSubject.next(config.defaultLanguage); } this.isInitializedSubject.next(true); this.startStatsPolling(); } catch (error) { console.error('UI18n初始化失败:', error); throw error; } finally { this.isLoadingSubject.next(false); } } /** * 初始化UI18n实例(企业版本) */ async initializeEnterprise(config) { try { this.isLoadingSubject.next(true); // 企业版配置直接使用createUI18n,因为EnterpriseUI18nConfig扩展了UI18nConfig this.ui18nInstance = createUI18n(config); // 设置当前语言 if (config.defaultLanguage) { this.currentLanguageSubject.next(config.defaultLanguage); } this.isInitializedSubject.next(true); this.startStatsPolling(); } catch (error) { console.error('UI18n企业版初始化失败:', error); throw error; } finally { this.isLoadingSubject.next(false); } } /** * 翻译单个文本 */ async translate(text, targetLanguage) { if (!this.ui18nInstance) { throw new Error('UI18n未初始化,请先调用initialize()方法'); } const target = targetLanguage || this.currentLanguageSubject.value; return await this.ui18nInstance.translate(text, target); } /** * 翻译单个文本(简化版本) */ async t(text, targetLanguage) { const result = await this.translate(text, targetLanguage); return result.text; } /** * 批量翻译(基础版本) */ async translateBatch(texts, targetLanguage) { if (!this.ui18nInstance) { throw new Error('UI18n未初始化,请先调用initialize()方法'); } const target = targetLanguage || this.currentLanguageSubject.value; return await this.ui18nInstance.translateBatch(texts, target); } /** * 智能批量翻译(增强版本) */ async translateBatchOptimized(texts, options = {}) { if (!this.ui18nInstance) { throw new Error('UI18n未初始化,请先调用initialize()方法'); } const { language, context, maxConcurrency, priority = 'normal', useOptimizer = true, enableProgressTracking = true } = options; const targetLanguage = language || this.currentLanguageSubject.value; try { if (enableProgressTracking) { this.batchProgressSubject.next(0); } let results; if (useOptimizer) { // 使用智能批量优化器 results = await this.ui18nInstance.translateBatch(texts, targetLanguage, { context, maxConcurrency, useOptimizer: true, priority: priority }); } else { // 传统批量翻译 results = await this.ui18nInstance.translateBatch(texts, targetLanguage, { context, maxConcurrency }); } if (enableProgressTracking) { this.batchProgressSubject.next(1); } return results; } catch (error) { if (enableProgressTracking) { this.batchProgressSubject.next(0); } throw error; } } /** * 切换语言 */ async switchLanguage(language) { if (!this.ui18nInstance) { throw new Error('UI18n未初始化,请先调用initialize()方法'); } this.ui18nInstance.setLanguage(language); this.currentLanguageSubject.next(language); } /** * 检测语言 */ async detectLanguage(text) { if (!this.ui18nInstance) { throw new Error('UI18n未初始化,请先调用initialize()方法'); } // 语言检测功能需要通过LanguageDetector实现 const { LanguageDetector } = await import('@ui18n/core'); const result = LanguageDetector.detectTextLanguage(text); return result.language; } /** * 获取翻译统计 */ getStats() { if (!this.ui18nInstance) { return null; } // 使用核心包的实际API获取统计信息 return this.ui18nInstance.getSystemStatus(); } /** * 获取系统状态 */ getStatus() { if (!this.ui18nInstance) { return null; } // 使用核心包的实际API获取系统状态 return this.ui18nInstance.getSystemStatus(); } /** * 清理缓存 */ async clearCache(pattern) { if (!this.ui18nInstance) { throw new Error('UI18n未初始化,请先调用initialize()方法'); } await this.ui18nInstance.clearCache(pattern); this.updateCacheStats(); } /** * 优化缓存 */ async optimizeCache() { if (!this.ui18nInstance) { throw new Error('UI18n未初始化,请先调用initialize()方法'); } try { // 获取优化的缓存管理器 const optimizedCacheManager = this.ui18nInstance.optimizedCache; if (optimizedCacheManager && optimizedCacheManager.cleanup) { await optimizedCacheManager.cleanup(); } this.updateCacheStats(); } catch (error) { console.error('缓存优化失败:', error); } } /** * 预加载语言 */ async preloadLanguage(language) { if (!this.ui18nInstance) { throw new Error('UI18n未初始化,请先调用initialize()方法'); } const commonPhrases = ['Hello', 'Thank you', 'Please', 'Yes', 'No', 'Save', 'Cancel', 'Delete', 'Edit', 'View', 'Login', 'Logout', 'Settings', 'Help', 'About']; for (const phrase of commonPhrases) { try { await this.ui18nInstance.translate(phrase, language); } catch (error) { console.warn(`预加载短语失败: ${phrase}`, error); } } this.updateCacheStats(); } /** * 连接实时同步 */ async connectRealTimeSync(options = {}) { if (!this.ui18nInstance) { throw new Error('UI18n未初始化,请先调用initialize()方法'); } try { const versionControlManager = this.ui18nInstance.versionControlManager; if (versionControlManager && versionControlManager.reconnectRealTimeSync) { const success = await versionControlManager.reconnectRealTimeSync(); this.updateRealTimeSyncStatus(); return success; } return false; } catch (error) { console.error('连接实时同步失败:', error); this.syncErrorsSubject.next(this.syncErrorsSubject.value + 1); return false; } } /** * 断开实时同步 */ disconnectRealTimeSync() { if (!this.ui18nInstance) { return; } try { const versionControlManager = this.ui18nInstance.versionControlManager; if (versionControlManager) { console.log('断开实时同步连接'); this.updateRealTimeSyncStatus(); } } catch (error) { console.error('断开实时同步失败:', error); } } /** * 手动同步 */ async manualSync(language) { if (!this.ui18nInstance) { return false; } try { const versionControlManager = this.ui18nInstance.versionControlManager; if (versionControlManager) { if (language) { if (versionControlManager.syncLanguagePack) { await versionControlManager.syncLanguagePack(language); } } else { if (versionControlManager.syncAllLanguagePacks) { await versionControlManager.syncAllLanguagePacks(); } } this.updateRealTimeSyncStatus(); return true; } return false; } catch (error) { console.error('手动同步失败:', error); this.syncErrorsSubject.next(this.syncErrorsSubject.value + 1); return false; } } /** * 获取缓存统计 */ getCacheStats() { if (!this.ui18nInstance || !this.ui18nInstance.getCacheStats) { return null; } try { const stats = this.ui18nInstance.getCacheStats(); return { totalItems: stats.totalItems || 0, memoryUsageMB: stats.memoryUsageMB || 0, memoryLimitMB: stats.memoryLimitMB || 50, memoryUtilization: stats.memoryUtilization || 0, hits: stats.hits || 0, misses: stats.misses || 0, hitRate: stats.hitRate || 0, type: stats.type || 'memory', hotItems: stats.hotItems, coldItems: stats.coldItems, averageAccessCount: stats.averageAccessCount }; } catch (error) { console.warn('获取缓存统计失败:', error); return null; } } /** * 获取实时同步状态 */ getRealTimeSyncStatus() { if (!this.ui18nInstance) { return { enabled: false, connected: false, method: 'polling', syncErrors: this.syncErrorsSubject.value }; } try { const versionControlManager = this.ui18nInstance.versionControlManager; if (versionControlManager && versionControlManager.getRealTimeSyncStatus) { const status = versionControlManager.getRealTimeSyncStatus(); return { enabled: status.enabled, connected: status.connected, method: status.method, lastEvent: status.lastEvent, syncErrors: this.syncErrorsSubject.value }; } } catch (error) { console.warn('获取实时同步状态失败:', error); } return { enabled: false, connected: false, method: 'polling', syncErrors: this.syncErrorsSubject.value }; } /** * 获取当前语言 */ getCurrentLanguage() { return this.currentLanguageSubject.value; } /** * 检查是否已初始化 */ isInitialized() { return this.isInitializedSubject.value; } /** * 开始统计轮询 */ startStatsPolling() { // 每5秒更新一次统计信息 const interval = setInterval(() => { if (this.ui18nInstance) { const stats = this.getStats(); const status = this.getStatus(); this.statsSubject.next(stats); this.statusSubject.next(status); // 更新高级功能状态 this.updateCacheStats(); this.updateRealTimeSyncStatus(); } }, 5000); // 在组件销毁时清理定时器 this.destroy$.subscribe(() => { clearInterval(interval); }); } /** * 更新缓存统计 */ updateCacheStats() { const cacheStats = this.getCacheStats(); this.cacheStatsSubject.next(cacheStats); } /** * 更新实时同步状态 */ updateRealTimeSyncStatus() { const syncStatus = this.getRealTimeSyncStatus(); this.realTimeSyncStatusSubject.next(syncStatus); } /** * 销毁服务 */ ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); if (this.ui18nInstance) { this.ui18nInstance.destroy(); } } } _UI18nService2 = UI18nService; [_UI18nService, _initClass$9] = _applyDecs2305(_UI18nService2, [], _classDecs$9).c; _initClass$9(); var _UI18nAdvancedService2; let _initClass$8, _classDecs$8; /** * 批量翻译状态 */ /** * 语言检测状态 */ _classDecs$8 = [Injectable({ providedIn: 'root' })]; let _UI18nAdvancedService; /** * UI18n高级功能服务 * 提供类似React Hooks的响应式功能 */ class UI18nAdvancedService { constructor(ui18nService) { this.ui18nService = ui18nService; this.destroy$ = new Subject(); // 批量翻译状态 this.batchTranslationState$ = new BehaviorSubject({ translations: {}, translationArray: [], loading: false, error: null, progress: 0, completedCount: 0, totalCount: 0, failedTexts: [] }); // 语言检测状态 this.languageDetectionState$ = new BehaviorSubject({ detectedLanguage: null, confidence: 0, loading: false }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } /** * 使用批量翻译功能 * 返回响应式的批量翻译状态和操作方法 */ useBatchTranslation(texts, options = {}) { const state$ = this.batchTranslationState$.asObservable(); const translateBatch = async (textsToTranslate = texts, skipCache = false) => { if (textsToTranslate.length === 0) { return; } // 更新状态:开始翻译 this.updateBatchState({ loading: true, error: null, progress: 0, completedCount: 0, totalCount: textsToTranslate.length, failedTexts: [] }); try { const results = await this.ui18nService.translateBatchOptimized(textsToTranslate, { ...options, enableProgressTracking: true }); // 构建翻译映射和数组 const translations = {}; const translationArray = []; results.forEach((result, index) => { const originalText = textsToTranslate[index]; const translatedText = result.text; translations[originalText] = translatedText; translationArray.push(translatedText); }); // 更新状态:翻译完成 this.updateBatchState({ translations, translationArray, loading: false, progress: 1, completedCount: results.length, error: null }); } catch (error) { // 更新状态:翻译失败 this.updateBatchState({ loading: false, error: error, progress: 0 }); console.error('批量翻译失败:', error); } }; const refresh = () => translateBatch(texts, true); const retryFailed = () => { const currentState = this.batchTranslationState$.value; if (currentState.failedTexts.length > 0) { translateBatch(currentState.failedTexts, true); } }; // 自动执行初始翻译 if (texts.length > 0) { translateBatch(); } return { state$, translateBatch, refresh, retryFailed }; } /** * 使用语言检测功能 * 返回响应式的语言检测状态和操作方法 */ useLanguageDetection() { const state$ = this.languageDetectionState$.asObservable(); const detectLanguage = async text => { if (!text.trim()) { return; } // 更新状态:开始检测 this.updateLanguageDetectionState({ loading: true, detectedLanguage: null, confidence: 0 }); try { const language = await this.ui18nService.detectLanguage(text); // 更新状态:检测完成 this.updateLanguageDetectionState({ detectedLanguage: language, confidence: 0.8, // 默认置信度 loading: false }); } catch (error) { // 更新状态:检测失败 this.updateLanguageDetectionState({ loading: false, detectedLanguage: 'en', confidence: 0.5 }); console.error('语言检测失败:', error); } }; return { state$, detectLanguage }; } /** * 使用实时同步功能 * 返回实时同步状态和控制方法 */ useRealTimeSync(options = {}) { const status$ = this.ui18nService.realTimeSyncStatus$; const isConnected$ = this.ui18nService.isConnected$; const syncErrors$ = this.ui18nService.syncErrors$; const connect = () => this.ui18nService.connectRealTimeSync(options); const disconnect = () => this.ui18nService.disconnectRealTimeSync(); const manualSync = language => this.ui18nService.manualSync(language); return { status$, isConnected$, syncErrors$, connect, disconnect, manualSync }; } /** * 使用缓存优化功能 * 返回缓存状态和管理方法 */ useCacheOptimization(options = {}) { const stats$ = this.ui18nService.cacheStats$; const isHealthy$ = this.ui18nService.cacheHealthy$; const clearCache = pattern => this.ui18nService.clearCache(pattern); const optimizeCache = () => this.ui18nService.optimizeCache(); const preloadLanguage = language => this.ui18nService.preloadLanguage(language); return { stats$, isHealthy$, clearCache, optimizeCache, preloadLanguage }; } /** * 使用翻译功能(增强版本) * 提供响应式的单个文本翻译 */ useTranslation(text, options) { const translatedText$ = new BehaviorSubject(text); const loading$ = new BehaviorSubject(false); const error$ = new BehaviorSubject(null); const translate = async () => { if (!text || !this.ui18nService.isInitialized()) { return; } loading$.next(true); error$.next(null); try { const result = await this.ui18nService.translate(text, options === null || options === void 0 ? void 0 : options.language); translatedText$.next(result.text); } catch (err) { error$.next(err); translatedText$.next(text); // 失败时返回原文 } finally { loading$.next(false); } }; // 监听语言变化,自动重新翻译 this.ui18nService.currentLanguage$.pipe(distinctUntilChanged(), debounceTime(100), takeUntil(this.destroy$)).subscribe(() => { translate(); }); // 自动执行初始翻译 translate(); return { translatedText$: translatedText$.asObservable(), loading$: loading$.asObservable(), error$: error$.asObservable(), translate }; } /** * 使用组合状态 * 将多个状态组合成一个统一的状态流 */ useCombinedState() { return combineLatest([this.ui18nService.currentLanguage$, this.ui18nService.isInitialized$, this.ui18nService.isLoading$, this.ui18nService.stats$, this.ui18nService.realTimeSyncStatus$, this.ui18nService.cacheStats$]).pipe(map(([currentLanguage, isInitialized, isLoading, stats, syncStatus, cacheStats]) => ({ currentLanguage, isInitialized, isLoading, stats, syncStatus, cacheStats, isReady: isInitialized && !isLoading, isSyncConnected: (syncStatus === null || syncStatus === void 0 ? void 0 : syncStatus.connected) || false, cacheHealthy: cacheStats ? cacheStats.memoryUtilization < 0.8 : true }))); } /** * 更新批量翻译状态 */ updateBatchState(update) { const currentState = this.batchTranslationState$.value; this.batchTranslationState$.next({ ...currentState, ...update }); } /** * 更新语言检测状态 */ updateLanguageDetectionState(update) { const currentState = this.languageDetectionState$.value; this.languageDetectionState$.next({ ...currentState, ...update }); } } _UI18nAdvancedService2 = UI18nAdvancedService; [_UI18nAdvancedService, _initClass$8] = _applyDecs2305(_UI18nAdvancedService2, [], _classDecs$8).c; _initClass$8(); var _UI18nTranslateDirective; let _initClass$7, _classDecs$7, _textDecs, _init_text, _targetLanguageDecs, _init_targetLanguage, _fallbackTextDecs, _init_fallbackText, _loadingTextDecs$1, _init_loadingText$1, _ref$4; _classDecs$7 = [Directive({ selector: '[ui18nTranslate]' })]; let _UI18nTranslateDirect; _ref$4 = (_textDecs = Input('ui18nTranslate'), _targetLanguageDecs = Input('ui18nLang'), _fallbackTextDecs = Input('ui18nFallback'), _loadingTextDecs$1 = Input('ui18nLoading'), "text"); /** * UI18n翻译指令 * 用法: <div ui18nTranslate="Hello World"></div> * 或: <div ui18nTranslate="Hello World" [ui18nLang]="'en'"></div> */ class UI18nTranslateDirective { constructor(elementRef, renderer, ui18nService) { this.elementRef = elementRef; this.renderer = renderer; this.ui18nService = ui18nService; this[_ref$4] = _init_text(this, ''); this.targetLanguage = _init_targetLanguage(this); this.fallbackText = _init_fallbackText(this); this.loadingText = _init_loadingText$1(this, '翻译中...'); this.destroy$ = new Subject(); this.isTranslating = false; } ngOnInit() { // 监听语言变化 this.ui18nService.currentLanguage$.pipe(takeUntil(this.destroy$)).subscribe(() => { this.translateText(); }); // 监听初始化状态 this.ui18nService.isInitialized$.pipe(takeUntil(this.destroy$)).subscribe(initialized => { if (initialized) { this.translateText(); } }); } ngOnChanges(changes) { // 当输入属性变化时重新翻译 if (changes['text'] || changes['targetLanguage']) { this.translateText(); } } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } /** * 执行翻译 */ async translateText() { if (!this.text || this.isTranslating || !this.ui18nService.isInitialized()) { return; } try { this.isTranslating = true; // 显示加载状态 if (this.loadingText) { this.updateElementText(this.loadingText); } // 执行翻译 const translatedText = await this.ui18nService.t(this.text, this.targetLanguage); this.updateElementText(translatedText); } catch (error) { console.error('翻译失败:', error); // 使用fallback文本或原文本 const fallback = this.fallbackText || this.text; this.updateElementText(fallback); } finally { this.isTranslating = false; } } /** * 更新元素文本内容 */ updateElementText(text) { const element = this.elementRef.nativeElement; if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') { // 对于输入元素,更新placeholder this.renderer.setAttribute(element, 'placeholder', text); } else { // 对于其他元素,更新文本内容 this.renderer.setProperty(element, 'textContent', text); } } } _UI18nTranslateDirective = UI18nTranslateDirective; ({ e: [_init_text, _init_targetLanguage, _init_fallbackText, _init_loadingText$1], c: [_UI18nTranslateDirect, _initClass$7] } = _applyDecs2305(_UI18nTranslateDirective, [[_textDecs, 0, "text"], [_targetLanguageDecs, 0, "targetLanguage"], [_fallbackTextDecs, 0, "fallbackText"], [_loadingTextDecs$1, 0, "loadingText"]], _classDecs$7)); _initClass$7(); var _UI18nBatchTranslateDirective; let _initClass$6, _classDecs$6, _textsDecs, _init_texts, _optionsDecs, _init_options, _autoTranslateDecs, _init_autoTranslate, _displayModeDecs, _init_displayMode, _separatorDecs, _init_separator, _loadingTextDecs, _init_loadingText, _errorTextDecs, _init_errorText, _translationsReadyDecs, _init_translationsReady, _progressUpdateDecs, _init_progressUpdate, _translationErrorDecs, _init_translationError, _ref$3; _classDecs$6 = [Directive({ selector: '[ui18nBatchTranslate]' })]; let _UI18nBatchTranslateD; _ref$3 = (_textsDecs = Input('ui18nBatchTranslate'), _optionsDecs = Input(), _autoTranslateDecs = Input(), _displayModeDecs = Input(), _separatorDecs = Input(), _loadingTextDecs = Input(), _errorTextDecs = Input(), _translationsReadyDecs = Output(), _progressUpdateDecs = Output(), _translationErrorDecs = Output(), "texts"); /** * 批量翻译指令 * 用法: <div ui18nBatchTranslate [texts]="textArray" [options]="batchOptions" (translationsReady)="onReady($event)"></div> */ class UI18nBatchTranslateDirective { constructor(element, ui18nService, cdr) { this.element = element; this.ui18nService = ui18nService; this.cdr = cdr; this[_ref$3] = _init_texts(this, []); this.options = _init_options(this, {}); this.autoTranslate = _init_autoTranslate(this, true); this.displayMode = _init_displayMode(this, 'join'); this.separator = _init_separator(this, ', '); this.loadingText = _init_loadingText(this, '正在翻译...'); this.errorText = _init_errorText(this, '翻译失败'); this.translationsReady = _init_translationsReady(this, new EventEmitter()); this.progressUpdate = _init_progressUpdate(this, new EventEmitter()); this.translationError = _init_translationError(this, new EventEmitter()); this.destroy$ = new Subject(); this.originalContent = ''; this.isTranslating = false; this.lastTexts = []; this.lastLanguage = ''; this.originalContent = this.element.nativeElement.textContent || ''; } ngOnInit() { // 监听语言变化 this.ui18nService.currentLanguage$.pipe(takeUntil(this.destroy$)).subscribe(() => { if (this.autoTranslate && this.texts.length > 0) { this.translateTexts(); } }); // 监听批量进度 this.ui18nService.batchProgress$.pipe(takeUntil(this.destroy$)).subscribe(progress => { this.progressUpdate.emit(progress); }); // 初始翻译 if (this.autoTranslate && this.texts.length > 0) { this.translateTexts(); } } ngOnChanges(changes) { if (changes['texts'] && !changes['texts'].firstChange) { if (this.autoTranslate) { this.translateTexts(); } } } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } /** * 手动触发翻译 */ async translate() { await this.translateTexts(); } /** * 翻译文本数组 */ async translateTexts() { if (!this.texts || this.texts.length === 0 || this.isTranslating) { return; } if (!this.ui18nService.isInitialized()) { this.updateElementContent(this.texts); return; } const currentLanguage = this.ui18nService.getCurrentLanguage(); const textsKey = this.texts.join('|'); // 检查是否需要重新翻译 if (this.lastTexts.join('|') === textsKey && this.lastLanguage === currentLanguage) { return; } try { this.isTranslating = true; this.updateElementContent([this.loadingText]); // 使用增强的批量翻译 const results = await this.ui18nService.translateBatchOptimized(this.texts, { ...this.options, enableProgressTracking: true }); // 更新显示内容 const translatedTexts = results.map(result => result.text); this.updateElementContent(translatedTexts); // 缓存结果 this.lastTexts = [...this.texts]; this.lastLanguage = currentLanguage; // 发出事件 this.translationsReady.emit(results); this.cdr.markForCheck(); } catch (error) { console.error('批量翻译失败:', error); this.updateElementContent([this.errorText]); this.translationError.emit(error); } finally { this.isTranslating = false; } } /** * 更新元素内容 */ updateElementContent(texts) { let content = ''; switch (this.displayMode) { case 'join': content = texts.join(this.separator); break; case 'list': content = texts.map(text => `• ${text}`).join('\n'); break; case 'custom': // 自定义模式不更新内容,由用户通过事件处理 return; default: content = texts.join(this.separator); } if (this.element.nativeElement) { this.element.nativeElement.textContent = content; } } } _UI18nBatchTranslateDirective = UI18nBatchTranslateDirective; ({ e: [_init_texts, _init_options, _init_autoTranslate, _init_displayMode, _init_separator, _init_loadingText, _init_errorText, _init_translationsReady, _init_progressUpdate, _init_translationError], c: [_UI18nBatchTranslateD, _initClass$6] } = _applyDecs2305(_UI18nBatchTranslateDirective, [[_textsDecs, 0, "texts"], [_optionsDecs, 0, "options"], [_autoTranslateDecs, 0, "autoTranslate"], [_displayModeDecs, 0, "displayMode"], [_separatorDecs, 0, "separator"], [_loadingTextDecs, 0, "loadingText"], [_errorTextDecs, 0, "errorText"], [_translationsReadyDecs, 0, "translationsReady"], [_progressUpdateDecs, 0, "progressUpdate"], [_translationErrorDecs, 0, "translationError"]], _classDecs$6)); _initClass$6(); var _UI18nTranslatePipe2; let _initClass$5, _classDecs$5; _classDecs$5 = [Pipe({ name: 'ui18nTranslate', pure: false // 设置为非纯管道以响应语言变化 })]; let _UI18nTranslatePipe; /** * UI18n翻译管道 * 用法: {{ 'Hello World' | ui18nTranslate }} * 或: {{ 'Hello World' | ui18nTranslate:'en' }} */ class UI18nTranslatePipe { constructor(ui18nService, cdr) { this.ui18nService = ui18nService; this.cdr = cdr; this.destroy$ = new Subject(); this.lastText = ''; this.lastLang = ''; this.lastResult = ''; this.isTranslating = false; // 监听语言变化,触发重新翻译 this.ui18nService.currentLanguage$.pipe(takeUntil(this.destroy$)).subscribe(() => { this.lastLang = ''; // 重置缓存,强制重新翻译 this.cdr.markForCheck(); }); } async transform(text, targetLanguage) { if (!text) { return ''; } if (!this.ui18nService.isInitialized()) { return text; // 未初始化时返回原文 } const currentLang = targetLanguage || this.ui18nService.getCurrentLanguage(); // 检查缓存 if (this.lastText === text && this.lastLang === currentLang && this.lastResult) { return this.lastResult; } // 避免重复翻译 if (this.isTranslating) { return this.lastResult || text; } try { this.isTranslating = true; const translatedText = await this.ui18nService.t(text, targetLanguage); // 更新缓存 this.lastText = text; this.lastLang = currentLang; this.lastResult = translatedText; // 触发变更检测 this.cdr.markForCheck(); return translatedText; } catch (error) { console.error('管道翻译失败:', error); return text; // 翻译失败时返回原文 } finally { this.isTranslating = false; } } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } } _UI18nTranslatePipe2 = UI18nTranslatePipe; [_UI18nTranslatePipe, _initClass$5] = _applyDecs2305(_UI18nTranslatePipe2, [], _classDecs$5).c; _initClass$5(); var _LanguageSelectorComponent; let _initClass$4, _classDecs$4, _languagesDecs, _init_languages, _showSearchDecs, _init_showSearch, _showNativeNameDecs, _init_showNativeName, _showBothNamesDecs, _init_showBothNames, _disabledDecs, _init_disabled, _searchPlaceholderDecs, _init_searchPlaceholder, _noResultsTextDecs, _init_noResultsText, _languageChangeDecs, _init_languageChange, _ref$2; _classDecs$4 = [Component({ selector: 'ui18n-language-selector', template: ` <div class="ui18n-language-selector" [class.disabled]="disabled"> <!-- 当前语言显示 --> <button class="ui18n-current-language" [class.open]="isOpen" (click)="toggleDropdown()" [disabled]="disabled" type="button" > <span class="ui18n-flag" *ngIf="currentLanguageOption?.flag"> {{ currentLanguageOption.flag }} </span> <span class="ui18n-name"> {{ showNativeName ? currentLanguageOption?.nativeName : currentLanguageOption?.name }} </span> <span class="ui18n-arrow" [class.rotated]="isOpen">▼</span> </button> <!-- 语言下拉列表 --> <div class="ui18n-dropdown" *ngIf="isOpen" (click)="$event.stopPropagation()"> <!-- 搜索框 --> <div class="ui18n-search" *ngIf="showSearch"> <input type="text" [(ngModel)]="searchQuery" [placeholder]="searchPlaceholder" class="ui18n-search-input" (input)="onSearchChange()" /> </div> <!-- 语言列表 --> <div class="ui18n-language-list"> <button *ngFor="let lang of filteredLanguages" class="ui18n-language-option" [class.selected]="lang.code === currentLanguage" (click)="selectLanguage(lang.code)" type="button" > <span class="ui18n-flag" *ngIf="lang.flag">{{ lang.flag }}</span> <span class="ui18n-names"> <span class="ui18n-name">{{ lang.name }}</span> <span class="ui18n-native-name" *ngIf="showBothNames">{{ lang.nativeName }}</span> </span> <span class="ui18n-check" *ngIf="lang.code === currentLanguage">✓</span> </button> </div> <!-- 无结果提示 --> <div class="ui18n-no-results" *ngIf="filteredLanguages.length === 0"> {{ noResultsText }} </div> </div> </div> <!-- 遮罩层 --> <div class="ui18n-overlay" *ngIf="isOpen" (click)="closeDropdown()"></div> `, styleUrls: ['./language-selector.component.css'], changeDetection: ChangeDetectionStrategy.OnPush })]; let _LanguageSelectorComp; _ref$2 = (_languagesDecs = Input(), _showSearchDecs = Input(), _showNativeNameDecs = Input(), _showBothNamesDecs = Input(), _disabledDecs = Input(), _searchPlaceholderDecs = Input(), _noResultsTextDecs = Input(), _languageChangeDecs = Output(), "languages"); /** * 语言选择器组件 * 提供用户友好的语言切换界面 */ class LanguageSelectorComponent { constructor(ui18nService, cdr) { this.ui18nService = ui18nService; this.cdr = cdr; this[_ref$2] = _init_languages(this, [{ code: 'zh-CN', name: '简体中文', nativeName: '简体中文', flag: '🇨🇳' }, { code: 'zh-TW', name: '繁體中文', nativeName: '繁體中文', flag: '🇹🇼' }, { code: 'en-US', name: 'English (US)', nativeName: 'English (US)', flag: '🇺🇸' }, { code: 'en-GB', name: 'English (UK)', nativeName: 'English (UK)', flag: '🇬🇧' }, { code: 'ja-JP', name: '日本語', nativeName: '日本語', flag: '🇯🇵' }, { code: 'ko-KR', name: '한국어', nativeName: '한국어', flag: '🇰🇷' }, { code: 'fr-FR', name: 'Français', nativeName: 'Français', flag: '🇫🇷' }, { code: 'de-DE', name: 'Deutsch', nativeName: 'Deutsch', flag: '🇩🇪' }, { code: 'es-ES', name: 'Español', nativeName: 'Español', flag: '🇪🇸' }, { code: 'pt-BR', name: 'Português (Brasil)', nativeName: 'Português (Brasil)', flag: '🇧🇷' }, { code: 'ru-RU', name: 'Русский', nativeName: 'Русский', flag: '🇷🇺' }, { code: 'ar-SA', name: 'العربية', nativeName: 'العربية', flag: '🇸🇦' }]); this.showSearch = _init_showSearch(this, true); this.showNativeName = _init_showNativeName(this, true); this.showBothNames = _init_showBothNames(this, false); this.disabled = _init_disabled(this, false); this.searchPlaceholder = _init_searchPlaceholder(this, '搜索语言...'); this.noResultsText = _init_noResultsText(this, '未找到匹配的语言'); this.languageChange = _init_languageChange(this, new EventEmitter()); this.isOpen = false; this.searchQuery = ''; this.filteredLanguages = []; this.currentLanguage = 'zh-CN'; this.currentLanguageOption = void 0; this.destroy$ = new Subject(); } ngOnInit() { // 监听当前语言变化 this.ui18nService.currentLanguage$.pipe(takeUntil(this.destroy$)).subscribe(language => { this.currentLanguage = language; this.updateCurrentLanguageOption(); this.cdr.markForCheck(); }); // 初始化过滤列表 this.filteredLanguages = [...this.languages]; this.updateCurrentLanguageOption(); // 点击外部关闭下拉框 document.addEventListener('click', this.onDocumentClick.bind(this)); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); document.removeEventListener('click', this.onDocumentClick.bind(this)); } /** * 切换下拉框显示状态 */ toggleDropdown() { if (this.disabled) return; this.isOpen = !this.isOpen; if (this.isOpen) { this.searchQuery = ''; this.filteredLanguages = [...this.languages]; } this.cdr.markForCheck(); } /** * 关闭下拉框 */ closeDropdown() { this.isOpen = false; this.cdr.markForCheck(); } /** * 选择语言 */ async selectLanguage(language) { try { await this.ui18nService.switchLanguage(language); this.languageChange.emit(language); this.closeDropdown(); } catch (error) { console.error('切换语言失败:', error); } } /** * 搜索变化处理 */ onSearchChange() { const query = this.searchQuery.toLowerCase().trim(); if (!query) { this.filteredLanguages = [...this.languages]; } else { this.filteredLanguages = this.languages.filter(lang => lang.name.toLowerCase().includes(query) || lang.nativeName.toLowerCase().includes(query) || lang.code.toLowerCase().includes(query)); } this.cdr.markForCheck(); } /** * 文档点击处理 */ onDocumentClick(event) { const target = event.target; const selector = target.closest('.ui18n-language-selector'); if (!selector && this.isOpen) { this.closeDropdown(); } } /** * 更新当前语言选项 */ updateCurrentLanguageOption() { this.currentLanguageOption = this.languages.find(lang => lang.code === this.currentLanguage); } } _LanguageSelectorComponent = LanguageSelectorComponent; ({ e: [_init_languages, _init_showSearch, _init_showNativeName, _init_showBothNames, _init_disabled, _init_searchPlaceholder, _init_noResultsText, _init_languageChange], c: [_LanguageSelectorComp, _initClass$4] } = _applyDecs2305(_LanguageSelectorComponent, [[_languagesDecs, 0, "languages"], [_showSearchDecs, 0, "showSearch"], [_showNativeNameDecs, 0, "showNativeName"], [_showBothNamesDecs, 0, "showBothNames"], [_disabledDecs, 0, "disabled"], [_searchPlaceholderDecs, 0, "searchPlaceholder"], [_noResultsTextDecs, 0, "noResultsText"], [_languageChangeDecs, 0, "languageChange"]], _classDecs$4)); _initClass$4(); var _RealTimeSyncComponent; let _initClass$3, _classDecs$3, _showDetailsDecs, _init_showDetails, _showControlsDecs$1, _init_showControls$1, _syncOptionsDecs, _init_syncOptions, _autoConnectDecs, _init_autoConnect, _connectedDecs, _init_connected, _syncCompletedDecs, _init_syncCompleted, _syncErrorDecs, _init_syncError, _ref$1; _classDecs$3 = [Component({ selector: 'ui18n-realtime-sync', template: ` <div class="ui18n-realtime-sync" [class.connected]="syncStatus?.connected"> <!-- 状态指示器 --> <div class="ui18n-sync-indicator"> <div class="ui18n-status-dot" [class.connected]="syncStatus?.connected" [class.disconnected]="!syncStatus?.connected" [class.error]="syncErrors > 0" ></div> <span class="ui18n-status-text"> {{ getStatusText() }} </span> </div> <!-- 同步信息 --> <div class="ui18n-sync-info" *ngIf="showDetails"> <div class="ui18n-sync-method"> <label>同步方式:</label> <span>{{ syncStatus?.method || 'polling' }}</span> </div> <div class="ui18n-last-sync" *ngIf="lastSyncTime"> <label>上次同步:</label> <span>{{ formatTime(lastSyncTime) }}</span> </div> <div class="ui18n-sync-errors" *ngIf="syncErrors > 0"> <label>错误次数:</label> <span class="error">{{ syncErrors }}</span> </div> </div> <!-- 控制按钮 --> <div class="ui18n-sync-controls" *ngIf="showControls"> <button *ngIf="!syncStatus?.connected" (click)="connect()" [disabled]="isConnecting" class="ui18n-btn ui18n-btn-connect" > {{ isConnecting ? '连接中...' : '连接' }} </button> <button *ngIf="syncStatus?.connected" (click)="disconnect()" class="ui18n-btn ui18n-btn-disconnect" > 断开 </button> <button (click)="manualSync()" [disabled]="isSyncing" class="ui18n-btn ui18n-btn-sync" > {{ isSyncing ? '同步中...' : '手动同步' }} </button> <button (click)="clearErrors()" *ngIf="syncErrors > 0" class="ui18n-btn ui18n-btn-clear" > 清除错误 </button> </div> </div> `, styles: [` .ui18n-realtime-sync { padding: 12px; border: 1px solid #e0e0e0; border-radius: 6px; background: #f9f9f9; font-size: 14px; } .ui18n-realtime-sync.connected { border-color: #4caf50; background: #f1f8e9; } .ui18n-sync-indicator { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; } .ui18n-status-dot { width: 10px; height: 10px; border-radius: 50%; background: #bdbdbd; animation: pulse 2s infinite; } .ui18n-status-dot.connected { background: #4caf50; } .ui18n-status-dot.disconnected { background: #f44336; } .ui18n-status-dot.error { background: #ff9800; } .ui18n-status-text { font-weight: 500; } .ui18n-sync-info { margin-bottom: 12px; font-size: 13px; } .ui18n-sync-info > div { display: flex; justify-content: space-between; margin-bottom: 4px; } .ui18n-sync-info label { color: #666; } .ui18n-sync-info .error { color: #f44336; font-weight: 500; } .ui18n-sync-controls { display: flex; gap: 8px; flex-wrap: wrap; } .ui18n-btn { padding: 6px 12px; border: 1px solid #ddd; border-radius: 4px; background: white; cursor: pointer; font-size: 12px; transition: all 0.2s; } .ui18n-btn:hover:not(:disabled) { background: #f5f5f5; } .ui18n-btn:disabled { opacity: 0.6; cursor: not-allowed; } .ui18n-btn-connect { border-color: #4caf50; color: #4caf50; } .ui18n-btn-disconnect { border-color: #f44336; color: #f44336; } .ui18n-btn-sync { border-color: #2196f3; color: #2196f3; } .ui18n-btn-clear { border-color: #ff9800; color: #ff9800; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } `], changeDetection: ChangeDetectionStrategy.OnPush })]; let _RealTimeSyncComponen; _ref$1 = (_showDetailsDecs = Input(), _showControlsDecs$1 = Input(), _syncOptionsDecs = Input(), _autoConnectDecs = Input(), _connectedDecs = Output(), _syncCompletedDecs = Output(), _syncErrorDecs = Output(), "showDetails"); /** * 实时同步状态组件 * 显示和控制实时同步功能 */ class RealTimeSyncComponent { constructor(ui18nService, cdr) { this.ui18nService = ui18nService; this.cdr = cdr; this[_ref$1] = _init_showDetails(this, true); this.showControls = _init_showControls$1(this, true); this.syncOptions = _init_syncOptions(this, {}); this.autoConnect = _init_autoConnect(this, false); this.connected = _init_connected(this, new EventEmitter()); this.syncCompleted = _init_syncCompleted(this, new EventEmitter()); this.syncError = _init_syncError(this, new EventEmitter()); this.syncStatus = null; this.syncErrors = 0; this.lastSyncTime = null; this.isConnecting = false; this.isSyncing = false; this.destroy$ = new Subject(); } ngOnInit() { // 订阅实时同步状态 this.ui18nService.realTimeSyncStatus$.pipe(takeUntil(this.destroy$)).subscribe(status => { this.syncStatus = status; this.syncErrors = status.syn