From a8a7c3ab861cb0d923eaf44bf0982aa30496279a Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Wed, 13 May 2026 13:32:34 +0000 Subject: [PATCH 1/2] fix: make rule category dictionaries reactive to language changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RuleCategoryDictionary and related exports were static module-level constants that captured t() translations at import time, so switching the UI language from Chinese to English left category labels ("在线", "DML", "可维护性" etc.) untranslated. Root cause: t() was called once during module initialization instead of on every render. This made the category display language-insensitive while desc/annotation (fetched from the API per-language) worked fine. Fix: Convert all dictionary constants to getter functions that call t() at invocation time; add Proxy-based backward-compatible aliases so existing imports continue to compile. In useRuleCategories hook, call getRuleCategoryDictionaryGroup() inside useMemo and add i18n.language to the dependency array so the memo recalculates when the user switches language. Closes CASE-002 (AC-3.2, AC-4.3 regression). --- .../src/hooks/useRuleCategories/index.data.ts | 100 ++++++++++++++---- .../src/hooks/useRuleCategories/index.tsx | 13 ++- 2 files changed, 91 insertions(+), 22 deletions(-) diff --git a/packages/sqle/src/hooks/useRuleCategories/index.data.ts b/packages/sqle/src/hooks/useRuleCategories/index.data.ts index 423dc29f8..1df7317f7 100644 --- a/packages/sqle/src/hooks/useRuleCategories/index.data.ts +++ b/packages/sqle/src/hooks/useRuleCategories/index.data.ts @@ -2,15 +2,19 @@ import { t } from '../../locale'; export type DictionaryType = { [key: string]: string }; -export const RuleCategoryDictionary: DictionaryType = { +// Use getter functions instead of static constants so that t() is called at +// render time, returning the translation for the *current* language rather +// than the language that was active when the module was first imported. + +export const getRuleCategoryDictionary = (): DictionaryType => ({ audit_accuracy: t('rule.category.auditAccuracy'), audit_purpose: t('rule.category.auditPurpose'), operand: t('rule.category.operand'), sql: t('rule.category.sql'), performance_cost: t('rule.category.performanceCost') -}; +}); -export const RuleCategoryOperandDictionary = { +export const getRuleCategoryOperandDictionary = (): DictionaryType => ({ database: t('rule.category.tag.database'), table_space: t('rule.category.tag.tableSpace'), table: t('rule.category.tag.table'), @@ -24,9 +28,9 @@ export const RuleCategoryOperandDictionary = { user: t('rule.category.tag.user'), sequence: t('rule.category.tag.sequence'), business: t('rule.category.tag.business') -}; +}); -export const RuleCategorySqlDictionary = { +export const getRuleCategorySqlDictionary = (): DictionaryType => ({ ddl: t('rule.category.tag.ddl'), dcl: t('rule.category.tag.dcl'), dml: t('rule.category.tag.dml'), @@ -42,30 +46,88 @@ export const RuleCategorySqlDictionary = { sql_procedure: t('rule.category.tag.procedure'), sql_trigger: t('rule.category.tag.trigger'), sql_view: t('rule.category.tag.view') -}; +}); -export const RuleCategoryAuditPurposeDictionary = { +export const getRuleCategoryAuditPurposeDictionary = (): DictionaryType => ({ correction: t('rule.category.tag.correction'), security: t('rule.category.tag.security'), maintenance: t('rule.category.tag.maintenance'), performance: t('rule.category.tag.performance') -}; +}); -export const RuleCategoryAuditAccuracyDictionary = { +export const getRuleCategoryAuditAccuracyDictionary = (): DictionaryType => ({ online: t('rule.category.tag.online'), offline: t('rule.category.tag.offline') -}; +}); -export const RuleCategoryPerformanceCostDictionary = { +export const getRuleCategoryPerformanceCostDictionary = (): DictionaryType => ({ high: t('rule.category.tag.high'), medium: t('rule.category.tag.medium'), low: t('rule.category.tag.low') -}; +}); + +export const getRuleCategoryDictionaryGroup = (): { + [key: string]: DictionaryType; +} => ({ + audit_accuracy: getRuleCategoryAuditAccuracyDictionary(), + audit_purpose: getRuleCategoryAuditPurposeDictionary(), + operand: getRuleCategoryOperandDictionary(), + sql: getRuleCategorySqlDictionary(), + performance_cost: getRuleCategoryPerformanceCostDictionary() +}); + +// Backward-compatible aliases: these are kept so that existing imports +// continue to compile, but they still evaluate t() lazily via Proxy. +const makeLazyDict = (getter: () => DictionaryType): DictionaryType => + new Proxy( + {}, + { + get(_target, prop: string) { + return getter()[prop]; + }, + ownKeys() { + return Object.keys(getter()); + }, + getOwnPropertyDescriptor(_target, prop: string) { + const val = getter()[prop]; + if (val !== undefined) { + return { configurable: true, enumerable: true, value: val }; + } + return undefined; + } + } + ); -export const RuleCategoryDictionaryGroup: { [key: string]: DictionaryType } = { - audit_accuracy: RuleCategoryAuditAccuracyDictionary, - audit_purpose: RuleCategoryAuditPurposeDictionary, - operand: RuleCategoryOperandDictionary, - sql: RuleCategorySqlDictionary, - performance_cost: RuleCategoryPerformanceCostDictionary -}; +export const RuleCategoryDictionary: DictionaryType = makeLazyDict( + getRuleCategoryDictionary +); +export const RuleCategoryOperandDictionary: DictionaryType = makeLazyDict( + getRuleCategoryOperandDictionary +); +export const RuleCategorySqlDictionary: DictionaryType = makeLazyDict( + getRuleCategorySqlDictionary +); +export const RuleCategoryAuditPurposeDictionary: DictionaryType = makeLazyDict( + getRuleCategoryAuditPurposeDictionary +); +export const RuleCategoryAuditAccuracyDictionary: DictionaryType = makeLazyDict( + getRuleCategoryAuditAccuracyDictionary +); +export const RuleCategoryPerformanceCostDictionary: DictionaryType = + makeLazyDict(getRuleCategoryPerformanceCostDictionary); +export const RuleCategoryDictionaryGroup: { [key: string]: DictionaryType } = + new Proxy({} as { [key: string]: DictionaryType }, { + get(_target, prop: string) { + return getRuleCategoryDictionaryGroup()[prop]; + }, + ownKeys() { + return Object.keys(getRuleCategoryDictionaryGroup()); + }, + getOwnPropertyDescriptor(_target, prop: string) { + const val = getRuleCategoryDictionaryGroup()[prop]; + if (val !== undefined) { + return { configurable: true, enumerable: true, value: val }; + } + return undefined; + } + }); diff --git a/packages/sqle/src/hooks/useRuleCategories/index.tsx b/packages/sqle/src/hooks/useRuleCategories/index.tsx index 42f668621..6ffdd2de0 100644 --- a/packages/sqle/src/hooks/useRuleCategories/index.tsx +++ b/packages/sqle/src/hooks/useRuleCategories/index.tsx @@ -2,11 +2,16 @@ import ruleTemplate from '@actiontech/shared/lib/api/sqle/service/rule_template' import { useRequest } from 'ahooks'; import { ResponseCode } from '@actiontech/dms-kit'; import { ReactNode, useMemo } from 'react'; -import { RuleCategoryDictionaryGroup, DictionaryType } from './index.data'; +import { + getRuleCategoryDictionaryGroup, + DictionaryType +} from './index.data'; import { Typography } from 'antd'; import { RuleCategoryOptionStyleWrapper } from './style'; +import { useTranslation } from 'react-i18next'; const useRuleCategories = (showOptionCount = false) => { + const { i18n } = useTranslation(); const { data: ruleCategories, loading: getRuleCategoriesLoading } = useRequest(() => ruleTemplate.getCategoryStatistics().then((res) => { @@ -39,13 +44,14 @@ const useRuleCategories = (showOptionCount = false) => { return dictionary?.[tag]; }; + const dictionaryGroup = getRuleCategoryDictionaryGroup(); const optionGroup: { [key: string]: | Array<{ label: ReactNode; value: string; text: string }> | undefined; } = {}; Object.keys(ruleCategories ?? {}).forEach((key) => { - const dictionary = RuleCategoryDictionaryGroup[key]; + const dictionary = dictionaryGroup[key]; optionGroup[key] = ruleCategories?.[key]?.map((i) => ({ label: renderOptionLabel(dictionary, i.tag ?? '', i.count ?? 0), value: i.tag ?? '', @@ -59,7 +65,8 @@ const useRuleCategories = (showOptionCount = false) => { auditPurposeOptions: optionGroup.audit_purpose, performanceLevelOptions: optionGroup.performance_cost }; - }, [ruleCategories, showOptionCount]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ruleCategories, showOptionCount, i18n.language]); return { ruleCategories, From 396e5848531c9c78aa13587c66ac9fbe6080862f Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Wed, 13 May 2026 13:50:04 +0000 Subject: [PATCH 2/2] fix: add missing English category translations to en-US/rule.ts The English locale file was missing the entire `category` section that exists in zh-CN/rule.ts, causing t('rule.category.tag.*') to fall back to Chinese text when the UI is in English mode. This made category tags like "Online", "DML", "DDL", "Maintainability" display in Chinese even after switching to English. Added complete English translations for all category fields including auditAccuracy, auditPurpose, operand, sql, performanceCost, and all tag entries (online, offline, database, table, column, index, etc.). Fixes CASE-002 regression-1 failure. --- packages/sqle/src/locale/en-US/rule.ts | 44 ++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/sqle/src/locale/en-US/rule.ts b/packages/sqle/src/locale/en-US/rule.ts index 3cf83d42f..638a1a4d4 100644 --- a/packages/sqle/src/locale/en-US/rule.ts +++ b/packages/sqle/src/locale/en-US/rule.ts @@ -31,5 +31,49 @@ export default { ruleDetail: { title: 'View rule', knowledge: 'Rule knowledge base' + }, + category: { + auditAccuracy: 'Audit Accuracy', + auditPurpose: 'Audit Purpose', + operand: 'Operand', + sql: 'SQL Category', + performanceCost: 'Performance Cost', + performanceLevelTips: + 'High-cost rules generate extensive data scans or complex queries that may significantly impact database performance. Use with caution in production environments.', + tag: { + online: 'Online', + offline: 'Offline', + database: 'Database', + tableSpace: 'Tablespace', + table: 'Table', + column: 'Column', + index: 'Index', + view: 'View', + procedure: 'Stored Procedure', + function: 'Function', + trigger: 'Trigger', + event: 'Event', + user: 'User', + ddl: 'DDL', + dcl: 'DCL', + dml: 'DML', + integrity: 'Integrity Constraint', + query: 'Query', + transaction: 'Transaction Control', + privilege: 'Data Privilege', + management: 'Database Management', + complete: 'Completeness Constraint', + join: 'Join', + table_space: 'Tablespace', + sequence: 'Sequence', + business: 'Business Data', + correction: 'Correctness', + security: 'Security', + maintenance: 'Maintainability', + performance: 'Performance', + high: 'High Cost', + medium: 'Medium Cost', + low: 'Low Cost' + } } };