mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-22 05:11:44 +08:00
feat(i18n): 收口多语言功能业务代码
This commit is contained in:
@@ -1,19 +1,35 @@
|
||||
export type SqlExecutionErrorFormatOptions = {
|
||||
prefix?: string;
|
||||
translate?: SqlExecutionErrorTranslator;
|
||||
};
|
||||
|
||||
export type SqlExecutionErrorTranslator = (
|
||||
key: string,
|
||||
params?: Record<string, string | number | boolean | null | undefined>,
|
||||
) => string;
|
||||
|
||||
type SqlErrorSemanticRule = {
|
||||
label: string;
|
||||
explanation: string;
|
||||
suggestion: string;
|
||||
key: string;
|
||||
fallbackLabel: string;
|
||||
fallbackExplanation: string;
|
||||
fallbackSuggestion: string;
|
||||
patterns: RegExp[];
|
||||
};
|
||||
|
||||
const LOCALIZED_TIMEOUT_KEYWORDS = [
|
||||
'\u8d85\u65f6',
|
||||
'\u903e\u6642',
|
||||
'\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8',
|
||||
'zeit\u00fcberschreitung',
|
||||
'\u0442\u0430\u0439\u043c-\u0430\u0443\u0442',
|
||||
] as const;
|
||||
|
||||
const SQL_ERROR_RULES: SqlErrorSemanticRule[] = [
|
||||
{
|
||||
label: 'SQL 语法错误',
|
||||
explanation: '通常是关键字、逗号、括号、引号、语句顺序或当前数据库方言不匹配。',
|
||||
suggestion: '检查报错位置附近的 SQL 片段,并确认当前连接的数据源类型与 SQL 方言一致。',
|
||||
key: 'syntax',
|
||||
fallbackLabel: 'SQL syntax error',
|
||||
fallbackExplanation: 'Usually caused by keywords, commas, parentheses, quotes, statement order, or SQL dialect mismatch.',
|
||||
fallbackSuggestion: 'Check the SQL fragment near the reported position and confirm the current data source type matches the SQL dialect.',
|
||||
patterns: [
|
||||
/syntax error/i,
|
||||
/sql syntax/i,
|
||||
@@ -25,9 +41,10 @@ const SQL_ERROR_RULES: SqlErrorSemanticRule[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '表或对象不存在',
|
||||
explanation: 'SQL 引用了当前库或 schema 中找不到的表、视图、序列或其他数据库对象。',
|
||||
suggestion: '确认对象名称、大小写、schema/database 前缀,以及当前查询所选数据库是否正确。',
|
||||
key: 'object_missing',
|
||||
fallbackLabel: 'Table or object does not exist',
|
||||
fallbackExplanation: 'The SQL references a table, view, sequence, or other database object that cannot be found in the current database or schema.',
|
||||
fallbackSuggestion: 'Check the object name, casing, schema/database prefix, and whether the selected database for this query is correct.',
|
||||
patterns: [
|
||||
/relation\s+["'`].+["'`]\s+does not exist/i,
|
||||
/table\s+.+doesn'?t exist/i,
|
||||
@@ -38,9 +55,10 @@ const SQL_ERROR_RULES: SqlErrorSemanticRule[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '字段不存在',
|
||||
explanation: 'SQL 引用了结果集中不存在、拼写不一致或当前表没有的字段。',
|
||||
suggestion: '检查字段名、别名、大小写、引用表别名,以及字段是否属于当前 FROM/JOIN 的对象。',
|
||||
key: 'column_missing',
|
||||
fallbackLabel: 'Column does not exist',
|
||||
fallbackExplanation: 'The SQL references a column that is not in the result set, is spelled differently, or does not exist on the current table.',
|
||||
fallbackSuggestion: 'Check column names, aliases, casing, table aliases, and whether the column belongs to the current FROM/JOIN object.',
|
||||
patterns: [
|
||||
/column\s+["'`].+["'`]\s+does not exist/i,
|
||||
/unknown column/i,
|
||||
@@ -50,9 +68,10 @@ const SQL_ERROR_RULES: SqlErrorSemanticRule[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '唯一约束或主键冲突',
|
||||
explanation: '插入或更新的数据与唯一索引、主键或唯一约束中的已有数据重复。',
|
||||
suggestion: '检查重复键值,必要时改为 UPDATE、UPSERT,或调整唯一键字段值。',
|
||||
key: 'unique_conflict',
|
||||
fallbackLabel: 'Unique constraint or primary key conflict',
|
||||
fallbackExplanation: 'The inserted or updated data duplicates an existing value in a unique index, primary key, or unique constraint.',
|
||||
fallbackSuggestion: 'Check the duplicate key value and use UPDATE or UPSERT if appropriate, or adjust the unique-key field value.',
|
||||
patterns: [
|
||||
/duplicate key/i,
|
||||
/duplicate entry/i,
|
||||
@@ -62,9 +81,10 @@ const SQL_ERROR_RULES: SqlErrorSemanticRule[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '权限不足',
|
||||
explanation: '当前数据库账号没有执行该 SQL 或访问相关对象的权限。',
|
||||
suggestion: '确认账号权限、schema 授权、只读连接限制,以及是否需要由管理员授权。',
|
||||
key: 'permission_denied',
|
||||
fallbackLabel: 'Insufficient permissions',
|
||||
fallbackExplanation: 'The current database account does not have permission to execute this SQL or access the related objects.',
|
||||
fallbackSuggestion: 'Check account privileges, schema grants, read-only connection limits, and whether an administrator needs to grant access.',
|
||||
patterns: [
|
||||
/permission denied/i,
|
||||
/access denied/i,
|
||||
@@ -74,9 +94,10 @@ const SQL_ERROR_RULES: SqlErrorSemanticRule[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '数据类型或格式不匹配',
|
||||
explanation: '写入、比较或转换的数据格式不符合目标字段或表达式要求。',
|
||||
suggestion: '检查日期、数字、布尔值、枚举值、隐式转换和字段类型,必要时显式 CAST。',
|
||||
key: 'type_mismatch',
|
||||
fallbackLabel: 'Data type or format mismatch',
|
||||
fallbackExplanation: 'The value being written, compared, or converted does not match the target column or expression format.',
|
||||
fallbackSuggestion: 'Check dates, numbers, booleans, enum values, implicit casts, and column types; use an explicit CAST if needed.',
|
||||
patterns: [
|
||||
/invalid input syntax/i,
|
||||
/incorrect\s+.+\s+value/i,
|
||||
@@ -88,9 +109,10 @@ const SQL_ERROR_RULES: SqlErrorSemanticRule[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '约束校验失败',
|
||||
explanation: '数据不满足外键、非空、检查约束或引用完整性规则。',
|
||||
suggestion: '检查关联父表记录、必填字段、CHECK 条件,以及写入顺序是否正确。',
|
||||
key: 'constraint_failed',
|
||||
fallbackLabel: 'Constraint validation failed',
|
||||
fallbackExplanation: 'The data violates a foreign key, non-null, check constraint, or referential integrity rule.',
|
||||
fallbackSuggestion: 'Check related parent records, required fields, CHECK conditions, and whether the write order is correct.',
|
||||
patterns: [
|
||||
/foreign key constraint/i,
|
||||
/violates foreign key constraint/i,
|
||||
@@ -101,9 +123,10 @@ const SQL_ERROR_RULES: SqlErrorSemanticRule[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '查询超时或被取消',
|
||||
explanation: 'SQL 执行时间超过超时限制,或执行过程被手动取消。',
|
||||
suggestion: '检查 SQL 执行计划、过滤条件和索引,必要时缩小查询范围或调整超时时间。',
|
||||
key: 'timeout_or_canceled',
|
||||
fallbackLabel: 'Query timed out or was canceled',
|
||||
fallbackExplanation: 'The SQL ran longer than the timeout limit, or execution was manually canceled.',
|
||||
fallbackSuggestion: 'Check the SQL execution plan, filters, and indexes; narrow the query range or adjust the timeout if needed.',
|
||||
patterns: [
|
||||
/context deadline exceeded/i,
|
||||
/statement canceled/i,
|
||||
@@ -115,9 +138,10 @@ const SQL_ERROR_RULES: SqlErrorSemanticRule[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '数据库连接或认证失败',
|
||||
explanation: '客户端无法连接数据库,或认证信息、网络、实例状态存在问题。',
|
||||
suggestion: '检查主机、端口、账号密码、网络连通性、代理/SSH 隧道和数据库服务状态。',
|
||||
key: 'connection_or_auth',
|
||||
fallbackLabel: 'Database connection or authentication failed',
|
||||
fallbackExplanation: 'The client could not connect to the database, or credentials, network, or instance state may be wrong.',
|
||||
fallbackSuggestion: 'Check host, port, username, password, network reachability, proxy/SSH tunnel, and database service status.',
|
||||
patterns: [
|
||||
/password authentication failed/i,
|
||||
/connection refused/i,
|
||||
@@ -130,6 +154,17 @@ const SQL_ERROR_RULES: SqlErrorSemanticRule[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const GENERIC_SQL_ERROR_RULE: SqlErrorSemanticRule = {
|
||||
key: 'generic',
|
||||
fallbackLabel: 'Database execution error',
|
||||
fallbackExplanation: 'The database returned an execution failure, and no more specific error type was matched.',
|
||||
fallbackSuggestion: 'Continue troubleshooting with the raw error, SQL fragment, and current database dialect.',
|
||||
patterns: [],
|
||||
};
|
||||
|
||||
const LEGACY_SEMANTIC_PREFIX = '\u4e2d\u6587\u8bed\u4e49\uff1a';
|
||||
const LEGACY_RAW_PREFIX = '\u539f\u59cb\u9519\u8bef\uff1a';
|
||||
|
||||
const normalizeErrorText = (raw: unknown): string => {
|
||||
if (raw instanceof Error) {
|
||||
return raw.message || String(raw);
|
||||
@@ -147,31 +182,93 @@ const normalizeErrorText = (raw: unknown): string => {
|
||||
}
|
||||
};
|
||||
|
||||
const includesLocalizedKeyword = (
|
||||
message: string,
|
||||
keywords: readonly string[],
|
||||
): boolean => {
|
||||
const lower = String(message || '').toLowerCase();
|
||||
return keywords.some((keyword) => lower.includes(keyword.toLowerCase()));
|
||||
};
|
||||
|
||||
export const hasLocalizedSqlTimeoutKeyword = (message: string): boolean =>
|
||||
includesLocalizedKeyword(message, LOCALIZED_TIMEOUT_KEYWORDS);
|
||||
|
||||
const findSqlErrorSemantic = (message: string): SqlErrorSemanticRule | null => {
|
||||
const text = String(message || '');
|
||||
return SQL_ERROR_RULES.find((rule) => rule.patterns.some((pattern) => pattern.test(text))) || null;
|
||||
const matchedRule = SQL_ERROR_RULES.find((rule) => rule.patterns.some((pattern) => pattern.test(text)));
|
||||
if (matchedRule) {
|
||||
return matchedRule;
|
||||
}
|
||||
if (hasLocalizedSqlTimeoutKeyword(text)) {
|
||||
return SQL_ERROR_RULES.find((rule) => rule.key === 'timeout_or_canceled') || null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const translateSqlErrorCopy = (
|
||||
translate: SqlExecutionErrorTranslator | undefined,
|
||||
key: string,
|
||||
fallback: string,
|
||||
params?: Record<string, string | number | boolean | null | undefined>,
|
||||
): string => {
|
||||
if (!translate) {
|
||||
return fallback;
|
||||
}
|
||||
const translated = translate(key, params);
|
||||
return translated && translated !== key ? translated : fallback;
|
||||
};
|
||||
|
||||
const localizeRule = (
|
||||
rule: SqlErrorSemanticRule,
|
||||
translate?: SqlExecutionErrorTranslator,
|
||||
) => {
|
||||
const baseKey = `query_editor.sql_error.rule.${rule.key}`;
|
||||
return {
|
||||
label: translateSqlErrorCopy(translate, `${baseKey}.label`, rule.fallbackLabel),
|
||||
explanation: translateSqlErrorCopy(translate, `${baseKey}.explanation`, rule.fallbackExplanation),
|
||||
suggestion: translateSqlErrorCopy(translate, `${baseKey}.suggestion`, rule.fallbackSuggestion),
|
||||
};
|
||||
};
|
||||
|
||||
export const formatSqlExecutionError = (
|
||||
raw: unknown,
|
||||
options: SqlExecutionErrorFormatOptions = {},
|
||||
): string => {
|
||||
const rawMessage = normalizeErrorText(raw).trim() || '未知错误';
|
||||
if (/中文语义:/.test(rawMessage) && /原始错误:/.test(rawMessage)) {
|
||||
const translate = options.translate;
|
||||
const rawMessage = normalizeErrorText(raw).trim() || translateSqlErrorCopy(
|
||||
translate,
|
||||
'query_editor.sql_error.unknown',
|
||||
'Unknown error',
|
||||
);
|
||||
if (rawMessage.includes(LEGACY_SEMANTIC_PREFIX) && rawMessage.includes(LEGACY_RAW_PREFIX)) {
|
||||
return rawMessage;
|
||||
}
|
||||
|
||||
const semantic = findSqlErrorSemantic(rawMessage) || {
|
||||
label: '数据库执行错误',
|
||||
explanation: '数据库返回了执行失败信息,当前未匹配到更具体的错误类型。',
|
||||
suggestion: '结合原始错误、SQL 片段和当前数据库方言继续排查。',
|
||||
};
|
||||
const semantic = localizeRule(findSqlErrorSemantic(rawMessage) || GENERIC_SQL_ERROR_RULE, translate);
|
||||
const prefix = String(options.prefix || '').trim();
|
||||
|
||||
return [
|
||||
prefix,
|
||||
`中文语义:${semantic.label}。${semantic.explanation}`,
|
||||
`处理建议:${semantic.suggestion}`,
|
||||
`原始错误:${rawMessage}`,
|
||||
translateSqlErrorCopy(
|
||||
translate,
|
||||
'query_editor.sql_error.wrapper.semantic_line',
|
||||
`Semantic meaning: ${semantic.label}. ${semantic.explanation}`,
|
||||
{
|
||||
label: semantic.label,
|
||||
explanation: semantic.explanation,
|
||||
},
|
||||
),
|
||||
translateSqlErrorCopy(
|
||||
translate,
|
||||
'query_editor.sql_error.wrapper.suggestion_line',
|
||||
`Suggestion: ${semantic.suggestion}`,
|
||||
{ suggestion: semantic.suggestion },
|
||||
),
|
||||
translateSqlErrorCopy(
|
||||
translate,
|
||||
'query_editor.sql_error.wrapper.raw_line',
|
||||
`Raw error: ${rawMessage}`,
|
||||
{ error: rawMessage },
|
||||
),
|
||||
].filter(Boolean).join('\n');
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user