feat: WWJCloud 企业级全栈框架 v0.3.5 完整更新
🚀 核心更新: - ✅ 完善 NestJS 企业级架构设计 - ✅ 优化配置中心和基础设施层 - ✅ 增强第三方服务集成能力 - ✅ 完善多租户架构支持 - 🎯 对标 Java Spring Boot 和 PHP ThinkPHP 📦 新增文件: - wwjcloud-nest 完整框架结构 - Docker 容器化配置 - 管理后台界面 - 数据库迁移脚本 🔑 Key: ebb38b43ec39f355f071294fd1cf9c42
This commit is contained in:
385
wwjcloud-nest/src/common/language/language.service.ts
Normal file
385
wwjcloud-nest/src/common/language/language.service.ts
Normal file
@@ -0,0 +1,385 @@
|
||||
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<string, Map<string, any>>();
|
||||
private readonly moduleCache = new Map<string, Set<string>>(); // 记录已加载的模块
|
||||
|
||||
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<string> {
|
||||
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<string> {
|
||||
return await this.getMessage(key, args, module, type, language);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化语言包
|
||||
* 只加载通用语言包,其他模块按需加载
|
||||
*/
|
||||
private async initializeLanguagePacks(): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
try {
|
||||
const langDir = path.join(process.cwd(), 'src', 'lang', language);
|
||||
const languagePack = new Map<string, any>();
|
||||
|
||||
// 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<string, any>): Promise<void> {
|
||||
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<string, any>): Promise<void> {
|
||||
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<string, any>): Promise<void> {
|
||||
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<string, any>, source: Record<string, any>): 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, any>): 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<void> {
|
||||
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<void> {
|
||||
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<string> {
|
||||
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<string> {
|
||||
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<string> {
|
||||
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<Record<string, string>> {
|
||||
const results: Record<string, string> = {};
|
||||
const currentLanguage = language || this.getCurrentLanguage();
|
||||
|
||||
for (const key of keys) {
|
||||
results[key] = await this.getMessage(key, undefined, module, type, currentLanguage);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user