import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { promises as fs } from 'fs'; import * as path from 'path'; import { ILanguageService } from './language.interface'; /** * 语言服务 * 基于 NestJS 实现模块化多语言支持 * 符合NestJS规范的服务层实现 * 对应PHP: think\Lang 类 * 对应Java: MessageSource 实现 */ @Injectable() export class LanguageService implements ILanguageService { private readonly logger = new Logger(LanguageService.name); private readonly languageCache = new Map>(); private readonly moduleCache = new Map>(); // 记录已加载的模块 constructor(private readonly configService: ConfigService) { this.initializeLanguagePacks(); } /** * 获取消息 * @param key 消息键 * @param args 参数 * @param module 模块名 * @param type 类型 (api|dict|validate) * @param language 语言 * @returns 消息内容 */ async getMessage( key: string, args?: any, module: string = 'common', type: string = 'api', language?: string, ): Promise { try { const currentLanguage = language || this.getCurrentLanguage(); const cacheKey = `${type}.${module}.${key}`; // 确保模块已加载 await this.ensureModuleLoaded(module, type, currentLanguage); const message = this.getMessageFromCache(cacheKey, currentLanguage); if (message && message !== key) { // 支持参数替换 if (args && typeof args === 'object') { return this.replaceMessageParams(message, args); } return message; } this.logger.warn( `未找到消息: ${key} (模块: ${module}, 类型: ${type}, 语言: ${currentLanguage})`, ); return key; } catch (error) { this.logger.warn(`获取消息失败: ${key}`, error); return key; } } /** * 获取消息(实例方法) * @param key 消息键 * @param args 参数 * @param module 模块名 * @param type 类型 * @param language 语言 * @returns 消息内容 */ async get( key: string, args?: any, module: string = 'common', type: string = 'api', language?: string, ): Promise { return await this.getMessage(key, args, module, type, language); } /** * 初始化语言包 * 只加载通用语言包,其他模块按需加载 */ private async initializeLanguagePacks(): Promise { try { const supportedLanguages = this.getSupportedLanguages(); for (const language of supportedLanguages) { // 只加载通用模块 await this.loadModuleLanguagePack('common', 'api', language); await this.loadModuleLanguagePack('common', 'dict', language); await this.loadModuleLanguagePack('common', 'validate', language); } this.logger.log('通用语言包初始化完成'); } catch (error) { this.logger.error('语言包初始化失败', error); } } /** * 确保模块已加载 * @param module 模块名 * @param type 类型 * @param language 语言 */ private async ensureModuleLoaded( module: string, type: string, language: string, ): Promise { const moduleKey = `${module}.${type}`; const languageKey = `${language}.${moduleKey}`; if ( !this.moduleCache.has(language) || !this.moduleCache.get(language)!.has(moduleKey) ) { await this.loadModuleLanguagePack(module, type, language); } } /** * 加载模块语言包 * @param module 模块名 * @param type 类型 (api|dict|validate) * @param language 语言 */ private async loadModuleLanguagePack( module: string, type: string, language: string, ): Promise { try { const langDir = path.join(process.cwd(), 'src', 'lang', language); const languagePack = new Map(); // 1. 加载通用语言包 if (module !== 'common') { await this.loadCommonLanguagePack(langDir, type, languagePack); } // 2. 加载模块语言包 await this.loadModuleSpecificPack(langDir, module, type, languagePack); // 3. 加载插件语言包 await this.loadAddonLanguagePacks(langDir, type, languagePack); // 4. 缓存语言包 if (!this.languageCache.has(language)) { this.languageCache.set(language, new Map()); } const languageCache = this.languageCache.get(language)!; for (const [key, value] of languagePack) { languageCache.set(key, value); } // 5. 记录已加载的模块 if (!this.moduleCache.has(language)) { this.moduleCache.set(language, new Set()); } this.moduleCache.get(language)!.add(`${module}.${type}`); this.logger.log(`模块语言包加载完成: ${module}.${type} (${language})`); } catch (error) { this.logger.error( `加载模块语言包失败: ${module}.${type} (${language})`, error, ); } } /** * 加载通用语言包 */ private async loadCommonLanguagePack( langDir: string, type: string, languagePack: Map, ): Promise { const commonDir = path.join(langDir, 'common'); const filePath = path.join(commonDir, `${type}.json`); try { const content = await fs.readFile(filePath, 'utf8'); const data = JSON.parse(content); // 合并到语言包,添加前缀 for (const [key, value] of Object.entries(data)) { languagePack.set(`${type}.common.${key}`, value); } } catch (error) { this.logger.warn(`加载通用语言包失败: ${type}`, error); } } /** * 加载模块特定语言包 */ private async loadModuleSpecificPack( langDir: string, module: string, type: string, languagePack: Map, ): Promise { const moduleDir = path.join(langDir, module); const filePath = path.join(moduleDir, `${type}.json`); try { const content = await fs.readFile(filePath, 'utf8'); const data = JSON.parse(content); // 合并到语言包,添加前缀 for (const [key, value] of Object.entries(data)) { languagePack.set(`${type}.${module}.${key}`, value); } } catch (error) { this.logger.warn(`加载模块语言包失败: ${module}.${type}`, error); } } /** * 加载插件语言包 */ private async loadAddonLanguagePacks( langDir: string, type: string, languagePack: Map, ): Promise { const addonsDir = path.join(langDir, 'addons'); try { const addonDirs = await fs.readdir(addonsDir); for (const addonDir of addonDirs) { const addonPath = path.join(addonsDir, addonDir); const stat = await fs.stat(addonPath); if (stat.isDirectory()) { const filePath = path.join(addonPath, `${type}.json`); try { const content = await fs.readFile(filePath, 'utf8'); const data = JSON.parse(content); // 合并到语言包,添加前缀 for (const [key, value] of Object.entries(data)) { languagePack.set(`${type}.addon.${addonDir}.${key}`, value); } } catch (error) { this.logger.warn(`加载插件语言包失败: ${addonDir}.${type}`, error); } } } } catch (error) { this.logger.warn(`扫描插件语言包失败: ${type}`, error); } } /** * 合并语言数据 */ private mergeLanguageData( target: Map, source: Record, ): void { for (const [key, value] of Object.entries(source)) { target.set(key, value); } } /** * 从缓存获取消息 */ private getMessageFromCache(key: string, language: string): string { const languagePack = this.languageCache.get(language); if (languagePack && languagePack.has(key)) { return languagePack.get(key); } return key; // 未找到,返回key作为fallback } /** * 替换消息参数 * 对应 Java: MessageFormat.format() */ private replaceMessageParams( message: string, args: Record, ): string { let result = message; for (const [key, value] of Object.entries(args)) { const placeholder = `{${key}}`; result = result.replace(new RegExp(placeholder, 'g'), String(value)); } return result; } /** * 获取当前语言 * @returns 当前语言 */ getCurrentLanguage(): string { return this.getDefaultLanguage(); } /** * 设置语言 * @param language 语言代码 */ setLanguage(language: string): void { this.logger.log(`设置语言: ${language}`); } /** * 获取支持的语言列表 * @returns 支持的语言列表 */ getSupportedLanguages(): string[] { return this.configService.get('app.supportedLocales', ['zh_CN', 'en_US']); } /** * 获取默认语言 * @returns 默认语言 */ getDefaultLanguage(): string { return this.configService.get('app.defaultLanguage', 'zh_CN'); } /** * 检查语言是否支持 * @param language 语言代码 * @returns 是否支持 */ isLanguageSupported(language: string): boolean { const supportedLanguages = this.getSupportedLanguages(); return supportedLanguages.includes(language); } /** * 重新加载语言包 */ async reloadLanguagePack(language: string): Promise { try { // 清除该语言的所有缓存 this.languageCache.delete(language); this.moduleCache.delete(language); // 重新加载通用语言包 await this.loadModuleLanguagePack('common', 'api', language); await this.loadModuleLanguagePack('common', 'dict', language); await this.loadModuleLanguagePack('common', 'validate', language); this.logger.log(`重新加载语言包完成: ${language}`); } catch (error) { this.logger.error(`重新加载语言包失败: ${language}`, error); } } /** * 重新加载所有语言包 */ async reloadAllLanguagePacks(): Promise { try { this.languageCache.clear(); this.moduleCache.clear(); await this.initializeLanguagePacks(); this.logger.log('所有语言包重新加载完成'); } catch (error) { this.logger.error('重新加载所有语言包失败', error); } } /** * 获取API消息 * @param key 消息键 * @param args 参数 * @param module 模块名 * @param language 语言 */ async getApiMessage( key: string, args?: any, module: string = 'common', language?: string, ): Promise { const currentLanguage = language || this.getCurrentLanguage(); return this.getMessage(key, args, module, 'api', currentLanguage); } /** * 获取字典数据 * @param key 字典键 * @param args 参数 * @param module 模块名 * @param language 语言 */ async getDictData( key: string, args?: any, module: string = 'common', language?: string, ): Promise { const currentLanguage = language || this.getCurrentLanguage(); return this.getMessage(key, args, module, 'dict', currentLanguage); } /** * 获取验证器消息 * @param key 验证器键 * @param args 参数 * @param module 模块名 * @param language 语言 */ async getValidateMessage( key: string, args?: any, module: string = 'common', language?: string, ): Promise { const currentLanguage = language || this.getCurrentLanguage(); return this.getMessage(key, args, module, 'validate', currentLanguage); } /** * 批量获取消息 * @param keys 消息键数组 * @param module 模块名 * @param type 类型 * @param language 语言 */ async getBatchMessages( keys: string[], module: string = 'common', type: string = 'api', language?: string, ): Promise> { const results: Record = {}; const currentLanguage = language || this.getCurrentLanguage(); for (const key of keys) { results[key] = await this.getMessage( key, undefined, module, type, currentLanguage, ); } return results; } }