i18n.service.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import { Injectable } from '@nestjs/common';
  2. import { errorMessages, logMessages, statusMessages } from './messages';
  3. import { i18nStore } from './i18n.store';
  4. import { DEFAULT_LANGUAGE } from '../common/constants'; // 使用常量定义的默认语言
  5. @Injectable()
  6. export class I18nService {
  7. private readonly defaultLanguage = DEFAULT_LANGUAGE; // 使用常量定义的默认语言
  8. public normalizeLanguage(lang?: string): string {
  9. let language = lang;
  10. if (!language) {
  11. const store = i18nStore.getStore();
  12. language = store?.language;
  13. }
  14. if (!language) return this.defaultLanguage;
  15. // Normalize language codes (e.g., zh-CN -> zh, en-US -> en)
  16. const normalized = language.split('-')[0].toLowerCase();
  17. return normalized;
  18. }
  19. getErrorMessage(key: string, language?: string): string {
  20. const lang = this.normalizeLanguage(language);
  21. return errorMessages[lang]?.[key] || errorMessages[this.defaultLanguage][key] || key;
  22. }
  23. getLogMessage(key: string, language?: string): string {
  24. const lang = this.normalizeLanguage(language);
  25. return logMessages[lang]?.[key] || logMessages[this.defaultLanguage][key] || key;
  26. }
  27. getStatusMessage(key: string, language?: string): string {
  28. const lang = this.normalizeLanguage(language);
  29. return statusMessages[lang]?.[key] || statusMessages[this.defaultLanguage][key] || key;
  30. }
  31. // 汎用メッセージ取得メソッド、順次検索
  32. getMessage(key: string, language?: string): string {
  33. const lang = this.normalizeLanguage(language);
  34. // ステータスメッセージ、エラーメッセージ、ログメッセージの順に検索
  35. return statusMessages[lang]?.[key] ||
  36. statusMessages[this.defaultLanguage][key] ||
  37. errorMessages[lang]?.[key] ||
  38. errorMessages[this.defaultLanguage][key] ||
  39. logMessages[lang]?.[key] ||
  40. logMessages[this.defaultLanguage][key] ||
  41. key;
  42. }
  43. // メッセージの取得とフォーマット
  44. formatMessage(key: string, args: Record<string, any>, language?: string): string {
  45. let message = this.getMessage(key, language);
  46. for (const [argKey, argValue] of Object.entries(args)) {
  47. message = message.replace(new RegExp(`\\{${argKey}\\}`, 'g'), String(argValue));
  48. }
  49. return message;
  50. }
  51. // サポートされている言語リストを取得
  52. getSupportedLanguages(): string[] {
  53. return Object.keys(errorMessages);
  54. }
  55. // 言語がサポートされているか確認
  56. isLanguageSupported(language: string): boolean {
  57. return this.getSupportedLanguages().includes(language);
  58. }
  59. // システムプロンプトを取得
  60. getPrompt(lang: string = this.defaultLanguage, type: 'withContext' | 'withoutContext' = 'withContext', hasKnowledgeGroup: boolean = false): string {
  61. const language = this.normalizeLanguage(lang);
  62. const noMatchMsg = statusMessages[language]?.noMatchInKnowledgeGroup || statusMessages[this.defaultLanguage].noMatchInKnowledgeGroup;
  63. if (language === 'zh') {
  64. return type === 'withContext' ? `
  65. 基于以下知识库内容回答用户问题。
  66. ${hasKnowledgeGroup ? `
  67. **重要提示**: 用户已选择特定知识组,请严格基于以下知识库内容回答。如果知识库中没有相关信息,请明确告知用户:"${noMatchMsg}",然后再提供答案。
  68. ` : ''}
  69. 知识库内容:
  70. {context}
  71. 历史对话:
  72. {history}
  73. 用户问题:{question}
  74. 请用Chinese回答,并严格遵循以下 Markdown 格式要求:
  75. 1. **段落与结构**:
  76. - 使用清晰的段落分隔,每个要点之间空一行
  77. - 使用标题(## 或 ###)组织长回答
  78. 2. **文本格式**:
  79. - 使用 **粗体** 强调重要概念和关键词
  80. - 使用列表(- 或 1.)组织多个要点
  81. - 使用 \`代码\` 标记技术术语、命令、文件名
  82. 3. **代码展示**:
  83. - 使用代码块展示代码,并指定语言:
  84. \`\`\`python
  85. def example():
  86. return "示例"
  87. \`\`\`
  88. - 支持语言:python, javascript, typescript, java, bash, sql 等
  89. 4. **图表与可视化**:
  90. - 使用 Mermaid 语法绘制流程图、序列图等:
  91. \`\`\`mermaid
  92. graph LR
  93. A[开始] --> B[处理]
  94. B --> C[结束]
  95. \`\`\`
  96. - 适用场景:流程、架构、状态机、时序图
  97. 5. **其他要求**:
  98. - 回答精炼准确
  99. - 多步骤操作使用有序列表
  100. - 对比类信息建议用表格展示(如果适用)
  101. ` : `
  102. 作为智能助手,请回答用户的问题。
  103. 历史对话:
  104. {history}
  105. 用户问题:{question}
  106. 请用Chinese回答。
  107. `;
  108. } else if (language === 'ja') {
  109. return type === 'withContext' ? `
  110. 以下のナレッジベースの内容に基づいて、ユーザーの質問に答えてください。
  111. ${hasKnowledgeGroup ? `
  112. **重要**: ユーザーが特定のナレッジグループを選択しました。以下のナレッジベースの内容に厳密に基づいて回答してください。関連情報がナレッジベースに見つからない場合は、回答を提供する前に、ユーザーに明示的に「${noMatchMsg}」と伝えてください。
  113. ` : ''}
  114. ナレッジベースの内容:
  115. {context}
  116. 会話履歴:
  117. {history}
  118. ユーザーの質問:{question}
  119. 日本語で回答し、以下のMarkdown形式のガイドラインに厳密に従ってください。
  120. 1. **段落と構造**:
  121. - 明確な段落区切りを使用し、要点の間に空行を入れます
  122. - 見出し(## または ###)を使用して長い回答を整理します
  123. 2. **テキスト形式**:
  124. - **太字**を使用して重要な概念やキーワードを強調します
  125. - リスト(- または 1.)を使用して複数のポイントを整理します
  126. - \`コード\`を使用して技術用語、コマンド、ファイル名をマークします
  127. 3. **コード表示**:
  128. - 言語指定のあるコードブロックを使用します:
  129. \`\`\`python
  130. def example():
  131. return "示例"
  132. \`\`\`
  133. - サポートされている言語:python, javascript, typescript, java, bash, sqlなど
  134. 4. **図とチャート**:
  135. - フローチャート、シーケンス図などにMermaid構文を使用します:
  136. \`\`\`mermaid
  137. graph LR
  138. A[開始] --> B[処理]
  139. B --> C[終了]
  140. \`\`\`
  141. - 使用例:プロセスフロー、アーキテクチャ図、状態遷移図、シーケンス図
  142. 5. **その他の要件**:
  143. - 回答は簡潔かつ明確にします
  144. - マルチステップ プロセスには番号付きリストを使用します
  145. - 比較情報には表を使用します(該当する場合)
  146. ` : `
  147. インテリジェントなアシスタントとして、ユーザーの質問に答えてください。
  148. 会話履歴:
  149. {history}
  150. ユーザーの質問:{question}
  151. 日本語で回答してください。
  152. `;
  153. } else {
  154. // Fallback to English for any other language
  155. return type === 'withContext' ? `
  156. Answer the user's question based on the following knowledge base content.
  157. ${hasKnowledgeGroup ? `
  158. **IMPORTANT**: The user has selected a specific knowledge group. Please answer strictly based on the knowledge base content below. If the relevant information is not found in the knowledge base, explicitly tell the user: "${noMatchMsg}", before providing an answer.
  159. ` : ''}
  160. Knowledge Base CONTENT:
  161. {context}
  162. Conversation history:
  163. {history}
  164. User question: {question}
  165. Please answer in English and strictly follow these Markdown formatting guidelines:
  166. 1. **Paragraphs & Structure**:
  167. - Use clear paragraph breaks with blank lines between key points
  168. - Use headings (## or ###) to organize longer answers
  169. 2. **Text Formatting**:
  170. - Use **bold** to emphasize important concepts and keywords
  171. - Use lists (- or 1.) to organize multiple points
  172. - Use \`code\` to mark technical terms, commands, file names
  173. 3. **Code Display**:
  174. - Use code blocks with language specification:
  175. \`\`\`python
  176. def example():
  177. return "example"
  178. \`\`\`
  179. - Supported languages: python, javascript, typescript, java, bash, sql, etc.
  180. 4. **Diagrams & Charts**:
  181. - Use Mermaid syntax for flowcharts, sequence diagrams, etc.:
  182. \`\`\`mermaid
  183. graph LR
  184. A[Start] --> B[Process]
  185. B --> C[End]
  186. \`\`\`
  187. - Use cases: process flows, architecture diagrams, state diagrams, sequence diagrams
  188. 5. **Other Requirements**:
  189. - Keep answers concise and clear
  190. - Use numbered lists for multi-step processes
  191. - Use tables for comparison information (if applicable)
  192. ` : `
  193. As an intelligent assistant, please answer the user's question.
  194. Conversation history:
  195. {history}
  196. User question: {question}
  197. Please answer in English.
  198. `;
  199. }
  200. }
  201. // タイトル生成用のプロンプトを取得
  202. getDocumentTitlePrompt(lang: string = this.defaultLanguage, contentSample: string): string {
  203. const language = this.normalizeLanguage(lang);
  204. if (language === 'zh') {
  205. return `你是一个文档分析师。请阅读以下文本(文档开头部分),并生成一个简炼、专业的标题(不超过50个字符)。
  206. 只返回标题文本。不要包含任何解释性文字或前导词(如“标题是:”)。
  207. 语言:Chinese
  208. 文本内容:
  209. ${contentSample}`;
  210. } else if (language === 'ja') {
  211. return `あなたは文書分析の専門家です。以下のテキスト(文書の冒頭部分)を読み、簡潔で専門的なタイトル(50文字以内)を生成してください。
  212. タイトルのみを返してください。前置きや説明は不要です。
  213. 言語:Japanese
  214. テキスト:
  215. ${contentSample}`;
  216. } else {
  217. return `You are a document analyzer. Read the following text (start of a document) and generate a concise, professional title (max 50 chars).
  218. Return ONLY the title text. No preamble like "The title is...".
  219. Language: English
  220. Text:
  221. ${contentSample}`;
  222. }
  223. }
  224. getChatTitlePrompt(lang: string = this.defaultLanguage, userMessage: string, aiResponse: string): string {
  225. const language = this.normalizeLanguage(lang);
  226. if (language === 'zh') {
  227. return `根据以下对话片段,生成一个简短、描述性的标题(不超过50个字符),总结讨论的主题。
  228. 只返回标题文本。不要包含任何前导词。
  229. 语言:Chinese
  230. 片段:
  231. 用户: ${userMessage}
  232. 助手: ${aiResponse}`;
  233. } else if (language === 'ja') {
  234. return `以下の会話のスニペットに基づいて、話題を要約する短く説明的なタイトル(50文字以内)を生成してください。
  235. タイトルのみを返してください。前置きは不要です。
  236. 言語:Japanese
  237. スニペット:
  238. ユーザー: ${userMessage}
  239. アシスタント: ${aiResponse}`;
  240. } else {
  241. return `Based on the following conversation snippet, generate a short, descriptive title (max 50 chars) that summarizes the topic.
  242. Return ONLY the title. No preamble.
  243. Language: English
  244. Snippet:
  245. User: ${userMessage}
  246. Assistant: ${aiResponse}`;
  247. }
  248. }
  249. }