mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
fix(desktop): prevent raw i18n key from leaking in slash command descriptions
getLocalizedFallbackCommands previously passed a constructed key to t() without checking if the translation actually resolved. When the i18n table lacked an entry, t() returned the raw key (e.g. 'slashCmd.clear.description'), which dosubot flagged in PR #593. Fix: default to the static English description, only override when t(key) returns a different string. Add two tests covering missing-key fallback and partial-translation scenarios. Co-Authored-By: qwen3.6-plus <QwenLM@claude-code-best.win>
This commit is contained in:
parent
2eaae94d40
commit
b2dfeb11cf
@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
filterSlashCommands,
|
||||
findSlashToken,
|
||||
getLocalizedFallbackCommands,
|
||||
insertSlashTrigger,
|
||||
mergeSlashCommands,
|
||||
replaceSlashCommand,
|
||||
@ -143,4 +144,38 @@ describe('composerUtils', () => {
|
||||
expect(resolveSlashUiAction('context')).toEqual({ type: 'panel', command: 'context' })
|
||||
expect(resolveSlashUiAction('status')).toEqual({ type: 'panel', command: 'status' })
|
||||
})
|
||||
|
||||
it('falls back to the static English description when a translation key is missing', () => {
|
||||
// Simulate an i18n t() function that returns the raw key for missing entries
|
||||
// (this is what the real translate() does via zh[key] ?? en[key] ?? key).
|
||||
const mockT = (key: string) => key
|
||||
|
||||
const commands = getLocalizedFallbackCommands(mockT)
|
||||
const clearCmd = commands.find((c) => c.name === 'clear')
|
||||
expect(clearCmd?.description).toBe('Clear conversation history')
|
||||
expect(clearCmd?.description).not.toBe('slashCmd.clear.description')
|
||||
|
||||
// Verify every command renders a human-readable description, never a raw key
|
||||
for (const cmd of commands) {
|
||||
expect(cmd.description).not.toMatch(/^slashCmd\./)
|
||||
}
|
||||
})
|
||||
|
||||
it('uses the localized description when the translation key resolves to a real string', () => {
|
||||
const mockT = (key: string) => {
|
||||
const map: Record<string, string> = {
|
||||
'slashCmd.clear.description': '清空会话历史',
|
||||
}
|
||||
return map[key] ?? key
|
||||
}
|
||||
|
||||
const commands = getLocalizedFallbackCommands(mockT)
|
||||
const clearCmd = commands.find((c) => c.name === 'clear')
|
||||
expect(clearCmd?.description).toBe('清空会话历史')
|
||||
|
||||
// A command without a translated key should still fall back to English
|
||||
const mcpCmd = commands.find((c) => c.name === 'mcp')
|
||||
expect(mcpCmd?.description).toBe('Open available MCP tools for the current chat context')
|
||||
expect(mcpCmd?.description).not.toBe('slashCmd.mcp.description')
|
||||
})
|
||||
})
|
||||
|
||||
@ -79,13 +79,34 @@ export const FALLBACK_SLASH_COMMANDS: SlashCommandOption[] = [
|
||||
{ name: 'vim', description: 'Toggle vim editing mode' },
|
||||
]
|
||||
|
||||
/** Build localized fallback commands using the current locale */
|
||||
/** Build localized fallback commands using the current locale.
|
||||
*
|
||||
* Resolution order for each command's description:
|
||||
* 1. Localized string from the i18n table (zh -> en) when a key is registered.
|
||||
* 2. The static English description shipped in FALLBACK_SLASH_COMMANDS.
|
||||
*
|
||||
* This guarantees we never render a raw key (e.g. "slashCmd.foo.description")
|
||||
* in the UI even if a command is missing from SLASH_CMD_DESCRIPTION_KEYS or
|
||||
* its translation entry is absent.
|
||||
*/
|
||||
export function getLocalizedFallbackCommands(t: (key: TranslationKey) => string): SlashCommandOption[] {
|
||||
return FALLBACK_SLASH_COMMANDS.map((cmd) => ({
|
||||
name: cmd.name,
|
||||
description: t(SLASH_CMD_DESCRIPTION_KEYS[cmd.name] ?? ('slashCmd.' + cmd.name + '.description' as TranslationKey)),
|
||||
...(cmd.argumentHint && { argumentHint: cmd.argumentHint }),
|
||||
}))
|
||||
return FALLBACK_SLASH_COMMANDS.map((cmd) => {
|
||||
const key = SLASH_CMD_DESCRIPTION_KEYS[cmd.name]
|
||||
let description = cmd.description
|
||||
if (key) {
|
||||
const translated = t(key)
|
||||
// i18n returns the key itself when no translation is found; fall back to
|
||||
// the static English description in that case.
|
||||
if (translated && translated !== key) {
|
||||
description = translated
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: cmd.name,
|
||||
description,
|
||||
...(cmd.argumentHint && { argumentHint: cmd.argumentHint }),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export type SlashCommandOption = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user