重构 API 为 8 领域 + 应用层架构

将后端模块拆分为 domains、applications、shared 三层,结算计算器移入 domain 纯函数目录,API 路径与测试保持不变。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-02 14:48:41 +08:00
parent 14e49374ac
commit 4c92157299
47 changed files with 169 additions and 138 deletions

View File

@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common';
import { I18nService } from './i18n.service';
@Module({
providers: [I18nService],
exports: [I18nService],
})
export class I18nModule {}

View File

@@ -0,0 +1,55 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../../shared/prisma/prisma.service';
import { DEFAULT_LOCALE } from '@thebet365/shared';
const FALLBACK_ORDER = ['en-US', 'zh-CN', 'ms-MY'];
@Injectable()
export class I18nService {
constructor(private prisma: PrismaService) {}
async getMessages(locale: string) {
const messages = await this.prisma.i18nMessage.findMany({
where: { locale: { in: [locale, ...FALLBACK_ORDER] } },
});
const byKey: Record<string, Record<string, string>> = {};
for (const m of messages) {
if (!byKey[m.msgKey]) byKey[m.msgKey] = {};
byKey[m.msgKey][m.locale] = m.value;
}
const result: Record<string, string> = {};
for (const [key, locales] of Object.entries(byKey)) {
result[key] =
locales[locale] ||
FALLBACK_ORDER.map((l) => locales[l]).find(Boolean) ||
key;
}
return result;
}
async upsertMessage(msgKey: string, locale: string, value: string) {
return this.prisma.i18nMessage.upsert({
where: { msgKey_locale: { msgKey, locale } },
create: { msgKey, locale, value },
update: { value },
});
}
async listMissing() {
const keys = await this.prisma.i18nMessage.groupBy({ by: ['msgKey'] });
const locales = ['zh-CN', 'ms-MY', 'en-US'];
const missing = [];
for (const { msgKey } of keys) {
for (const locale of locales) {
const exists = await this.prisma.i18nMessage.findUnique({
where: { msgKey_locale: { msgKey, locale } },
});
if (!exists) missing.push({ msgKey, locale });
}
}
return missing;
}
}