Files
wwjcloud-nest-v1/wwjcloud-nest/src/common/language/language.service.ts

459 lines
12 KiB
TypeScript
Raw Normal View History

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;
}
}