From 16dcf865ed378dd20f24fdd34e81fb899a4643de Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Tue, 28 Jul 2026 02:22:35 -0700 Subject: [PATCH] fix(i18n): translate automation cadence labels (#11068) The cadence picker now resolves Hourly, Daily, Weekdays, Weekly, and Custom cron through the renderer i18n catalog, with corrected zh/ko/es translations. Supersedes #10044 (thanks @innocarpe) and #10045 (thanks @fsdwen). Fixes #10043 --- .../AutomationSchedulePicker.test.ts | 34 +++++++++++++++++-- .../automations/AutomationSchedulePicker.tsx | 24 ++++++++----- src/renderer/src/i18n/locales/en.json | 5 +++ src/renderer/src/i18n/locales/es.json | 7 +++- src/renderer/src/i18n/locales/ja.json | 5 +++ src/renderer/src/i18n/locales/ko.json | 7 +++- src/renderer/src/i18n/locales/zh.json | 7 +++- 7 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/renderer/src/components/automations/AutomationSchedulePicker.test.ts b/src/renderer/src/components/automations/AutomationSchedulePicker.test.ts index f5bc9c432..9d3837b19 100644 --- a/src/renderer/src/components/automations/AutomationSchedulePicker.test.ts +++ b/src/renderer/src/components/automations/AutomationSchedulePicker.test.ts @@ -1,6 +1,6 @@ import React from 'react' import { renderToStaticMarkup } from 'react-dom/server' -import { describe, expect, it } from 'vitest' +import { afterEach, describe, expect, it } from 'vitest' import type { AutomationDraft } from './AutomationEditorDialog' import { AutomationCustomCronPanel, @@ -9,9 +9,11 @@ import { } from './AutomationCustomCronPanel' import { AUTOMATION_SCHEDULE_PRESET_OPTIONS, + getAutomationSchedulePresetLabel, getSchedulePresetDraft } from './AutomationSchedulePicker' import { isValidAutomationCronSchedule } from '../../../../shared/automation-schedules' +import { i18n } from '@/i18n/i18n' const BASE_DRAFT: AutomationDraft = { name: '', @@ -33,8 +35,34 @@ const BASE_DRAFT: AutomationDraft = { } describe('AutomationSchedulePicker', () => { - it('offers custom cron as a selectable cadence', () => { - expect(AUTOMATION_SCHEDULE_PRESET_OPTIONS).toContainEqual(['custom', 'Custom cron']) + // Reset here, not after the assertion: a failed expect would otherwise leak the locale. + afterEach(async () => { + await i18n.changeLanguage('en') + }) + + it('provides an i18n key for every selectable cadence (#10043)', () => { + expect(AUTOMATION_SCHEDULE_PRESET_OPTIONS).toContainEqual([ + 'custom', + 'Custom cron', + 'auto.components.automations.AutomationSchedulePicker.ddba78647e' + ]) + for (const [value, fallbackLabel, labelKey] of AUTOMATION_SCHEDULE_PRESET_OPTIONS) { + expect(value).not.toBe('') + expect(fallbackLabel).not.toBe('') + expect(labelKey).toMatch( + /^auto\.components\.automations\.AutomationSchedulePicker\.[0-9a-f]{10}$/ + ) + } + }) + + it.each([ + ['zh', ['每小时', '每天', '工作日', '每周', '自定义 cron']], + ['ja', ['毎時', '毎日', '平日', '毎週', 'カスタム cron']], + ['ko', ['매시간', '매일', '평일', '매주', '사용자 지정 cron']], + ['es', ['Cada hora', 'Diario', 'Días laborables', 'Semanal', 'Cron personalizado']] + ])('translates every cadence option in %s', async (locale, labels) => { + await i18n.changeLanguage(locale) + expect(AUTOMATION_SCHEDULE_PRESET_OPTIONS.map(getAutomationSchedulePresetLabel)).toEqual(labels) }) it('seeds custom cron from the current simple schedule', () => { diff --git a/src/renderer/src/components/automations/AutomationSchedulePicker.tsx b/src/renderer/src/components/automations/AutomationSchedulePicker.tsx index eb203f9c3..1f6f6d96e 100644 --- a/src/renderer/src/components/automations/AutomationSchedulePicker.tsx +++ b/src/renderer/src/components/automations/AutomationSchedulePicker.tsx @@ -25,12 +25,20 @@ import { translate } from '@/i18n/i18n' const FIELD_CONTROL_CLASS = 'border-input bg-input/30 shadow-xs dark:bg-input/30' export const AUTOMATION_SCHEDULE_PRESET_OPTIONS = [ - ['hourly', 'Hourly'], - ['daily', 'Daily'], - ['weekdays', 'Weekdays'], - ['weekly', 'Weekly'], - ['custom', 'Custom cron'] -] as const satisfies readonly [AutomationSchedulePreset, string][] + ['hourly', 'Hourly', 'auto.components.automations.AutomationSchedulePicker.55b2ef82a4'], + ['daily', 'Daily', 'auto.components.automations.AutomationSchedulePicker.f0202f3a89'], + ['weekdays', 'Weekdays', 'auto.components.automations.AutomationSchedulePicker.57e83307d0'], + ['weekly', 'Weekly', 'auto.components.automations.AutomationSchedulePicker.837d902bba'], + ['custom', 'Custom cron', 'auto.components.automations.AutomationSchedulePicker.ddba78647e'] +] as const satisfies readonly (readonly [AutomationSchedulePreset, string, string])[] + +export function getAutomationSchedulePresetLabel([, fallbackLabel, labelKey]: readonly [ + AutomationSchedulePreset, + string, + string +]): string { + return translate(labelKey, fallbackLabel) +} const DAY_OPTIONS = [ ['0', 'Sunday'], @@ -191,9 +199,9 @@ export function AutomationSchedulePicker({ - {AUTOMATION_SCHEDULE_PRESET_OPTIONS.map(([value, presetLabel]) => ( + {AUTOMATION_SCHEDULE_PRESET_OPTIONS.map(([value, fallbackLabel, labelKey]) => ( - {presetLabel} + {getAutomationSchedulePresetLabel([value, fallbackLabel, labelKey])} ))} diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 252253011..1da5948ac 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -13359,6 +13359,11 @@ "d90981f766": "Time", "6b914c5fbb": "Day", "233b8c94b6": "Cadence", + "55b2ef82a4": "Hourly", + "f0202f3a89": "Daily", + "57e83307d0": "Weekdays", + "837d902bba": "Weekly", + "ddba78647e": "Custom cron", "b08ccb4d06": "hourly", "8cc026fd73": "weekly", "c3e39e17cf": "custom" diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 302f24f7b..512f4ccff 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -13336,9 +13336,14 @@ "d90981f766": "Tiempo", "6b914c5fbb": "Día", "233b8c94b6": "Cadencia", + "55b2ef82a4": "Cada hora", + "f0202f3a89": "Diario", + "57e83307d0": "Días laborables", + "837d902bba": "Semanal", + "ddba78647e": "Cron personalizado", "b08ccb4d06": "cada hora", "8cc026fd73": "semanalmente", - "c3e39e17cf": "costumbre" + "c3e39e17cf": "personalizado" }, "AutomationSessionField": { "f3c76dce51": "Reutilizar", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index a3b5555a8..3bcc69693 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -13336,6 +13336,11 @@ "d90981f766": "時間", "6b914c5fbb": "日", "233b8c94b6": "ケイデンス", + "55b2ef82a4": "毎時", + "f0202f3a89": "毎日", + "57e83307d0": "平日", + "837d902bba": "毎週", + "ddba78647e": "カスタム cron", "b08ccb4d06": "毎時", "8cc026fd73": "毎週", "c3e39e17cf": "カスタム" diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 91878a71e..786a783c2 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -13336,9 +13336,14 @@ "d90981f766": "시간", "6b914c5fbb": "요일", "233b8c94b6": "반복 주기", + "55b2ef82a4": "매시간", + "f0202f3a89": "매일", + "57e83307d0": "평일", + "837d902bba": "매주", + "ddba78647e": "사용자 지정 cron", "b08ccb4d06": "매시간", "8cc026fd73": "주간", - "c3e39e17cf": "custom" + "c3e39e17cf": "사용자 지정" }, "AutomationSessionField": { "f3c76dce51": "재사용", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 70c2933ab..261cc7a45 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -13336,9 +13336,14 @@ "d90981f766": "时间", "6b914c5fbb": "天", "233b8c94b6": "节奏", + "55b2ef82a4": "每小时", + "f0202f3a89": "每天", + "57e83307d0": "工作日", + "837d902bba": "每周", + "ddba78647e": "自定义 cron", "b08ccb4d06": "每小时", "8cc026fd73": "每周", - "c3e39e17cf": "风俗" + "c3e39e17cf": "自定义" }, "AutomationSessionField": { "f3c76dce51": "重复利用",