diff --git a/apps/web/components/github/action-presets-section.tsx b/apps/web/components/github/action-presets-section.tsx index 820c6e7050..f14f0c6105 100644 --- a/apps/web/components/github/action-presets-section.tsx +++ b/apps/web/components/github/action-presets-section.tsx @@ -24,22 +24,33 @@ import { computeEditorHeight, } from "@/components/settings/profile-edit/script-editor"; import type { ScriptPlaceholder } from "@/components/settings/profile-edit/script-editor-completions"; +import { Trans, useTranslation } from "react-i18next"; +import type { TFunction } from "i18next"; -const ACTION_PROMPT_PLACEHOLDERS: ScriptPlaceholder[] = [ - { - key: "url", - description: "URL of the PR or issue", - example: "https://github.com/org/repo/pull/42", - executor_types: [], - }, - { - key: "title", - description: "Title of the PR or issue", - example: "Fix login page crash", - executor_types: [], - }, -]; +// A function, not a const: `description` is copy, and a module-scope `t()` call +// would freeze at the boot locale (see docs/i18n.md). `key` is the substitution +// token and `example` is sample GitHub data — neither is translated. +function actionPromptPlaceholders(t: TFunction): ScriptPlaceholder[] { + return [ + { + key: "url", + description: t("github:placeholderActionUrl"), + example: "https://github.com/org/repo/pull/42", + executor_types: [], + }, + { + key: "title", + description: t("github:placeholderActionTitle"), + example: "Fix login page crash", + executor_types: [], + }, + ]; +} +// `label` is PERSISTED to workspace settings as part of `GitHubActionPreset` and +// is immediately editable in the row below, so it must stay locale-neutral: a +// preset seeded in one locale and saved unedited would keep that locale's text +// forever. Same contract as DEFAULT_PR_PRESETS in my-github/action-presets.ts. function newPreset(): GitHubActionPreset { return { id: `preset_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`, @@ -59,11 +70,12 @@ function PresetIconSelect({ isDirty: boolean; onChange: (v: string) => void; }) { + const { t } = useTranslation(); return ( onPatch({ label: e.target.value })} />
- Hint + {t("github:hint")} onPatch({ hint: e.target.value })} />
@@ -143,42 +156,64 @@ function PresetRow({ className="h-8 cursor-pointer text-xs" onClick={onToggle} > - {expanded ? "Hide prompt" : "Edit prompt"} + {expanded ? t("github:hidePrompt") : t("github:editPrompt")} - {expanded && ( -
-
- onPatch({ prompt_template: v })} - language="markdown" - height={computeEditorHeight(preset.prompt_template)} - lineNumbers="off" - placeholders={ACTION_PROMPT_PLACEHOLDERS} - /> -
-

- Type {"{{"} to see available placeholders.{" "} - {"{{url}}"} and{" "} - {"{{title}}"} are - substituted when the action runs. -

-
- )} + {expanded && } + + ); +} + +// Split out of PresetRow to keep that component under the 100-line lint cap. +function PresetPromptEditor({ + preset, + baseline, + onPatch, +}: { + preset: GitHubActionPreset; + baseline?: GitHubActionPreset; + onPatch: (patch: Partial) => void; +}) { + const { t } = useTranslation(); + const placeholders = useMemo(() => actionPromptPlaceholders(t), [t]); + return ( +
+
+ onPatch({ prompt_template: v })} + language="markdown" + height={computeEditorHeight(preset.prompt_template)} + lineNumbers="off" + placeholders={placeholders} + /> +
+

+ {/* The three `{{…}}` tokens are passed as values, never written into the + catalog, where i18next would interpolate them away. */} + + Type {"{{token}}"} to see available placeholders.{" "} + {"{{url}}"} and{" "} + {"{{title}}"} are + substituted when the action runs. + +

); } @@ -315,6 +350,7 @@ function usePresetDrafts(workspaceId: string): { } export function ActionPresetsSection({ workspaceId }: { workspaceId: string }) { + const { t } = useTranslation(); const { toast } = useToast(); const { prDraft, @@ -332,26 +368,26 @@ export function ActionPresetsSection({ workspaceId }: { workspaceId: string }) { const handleSave = useCallback(async () => { try { await save(); - toast({ description: "Quick actions saved", variant: "success" }); + toast({ description: t("github:quickActionsSaved"), variant: "success" }); } catch { - toast({ description: "Failed to save quick actions", variant: "error" }); + toast({ description: t("github:failedToSaveQuickActions"), variant: "error" }); throw new Error("Failed to save quick actions"); } - }, [save, toast]); + }, [save, t, toast]); useSettingsSaveContributor({ id: `github-action-presets:${workspaceId}`, revision: JSON.stringify([prDraft, issueDraft]), isDirty: dirty, canSave: !loading, - invalidReason: loading ? "Quick actions are still loading." : undefined, + invalidReason: loading ? t("github:quickActionsAreStillLoading") : undefined, save: handleSave, discard, }); return ( } @@ -373,10 +409,10 @@ export function ActionPresetsSection({ workspaceId }: { workspaceId: string }) { - Pull requests + {t("github:pullRequests")} - Issues + {t("github:issues")} @@ -384,7 +420,7 @@ export function ActionPresetsSection({ workspaceId }: { workspaceId: string }) { presets={prDraft} baseline={prBaseline} onChange={setPrDraft} - addLabel="Add PR action" + addLabel={t("github:addPrAction")} /> @@ -392,7 +428,7 @@ export function ActionPresetsSection({ workspaceId }: { workspaceId: string }) { presets={issueDraft} baseline={issueBaseline} onChange={setIssueDraft} - addLabel="Add issue action" + addLabel={t("github:addIssueAction")} /> diff --git a/apps/web/components/github/default-queries-section.tsx b/apps/web/components/github/default-queries-section.tsx index 5208ded9a8..4b46bc57bd 100644 --- a/apps/web/components/github/default-queries-section.tsx +++ b/apps/web/components/github/default-queries-section.tsx @@ -20,7 +20,11 @@ import { PR_PRESETS as BUILTIN_PR_PRESETS, ISSUE_PRESETS as BUILTIN_ISSUE_PRESETS, } from "@/components/github/my-github/search-bar"; +import { useTranslation } from "react-i18next"; +// `label` is PERSISTED as part of `StoredQueryPreset` (github_default_query_presets) +// and is editable in the row below, so it must stay locale-neutral — see +// `newPreset` in action-presets-section.tsx for the same contract. function newPreset(): StoredQueryPreset { return { value: `q_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`, @@ -41,6 +45,7 @@ function QueryRow({ onPatch: (patch: Partial) => void; onRemove: () => void; }) { + const { t } = useTranslation(); return (
- Label + {t("github:label")} onPatch({ label: e.target.value })} />
- Query + {t("github:query")} onPatch({ filter: e.target.value })} />
- Group + {t("github:group")} @@ -94,7 +99,7 @@ function QueryRow({ size="icon" className="h-8 w-8 cursor-pointer text-destructive mt-3.5" onClick={onRemove} - aria-label="Remove" + aria-label={t("github:remove")} > @@ -145,6 +150,7 @@ function QueryEditor({ } function useDefaultQueryDrafts(workspaceId?: string) { + const { t } = useTranslation(); const { toast } = useToast(); const { prPresets, issuePresets, save, reset, isCustomized, isReady } = useDefaultQueryPresets( workspaceId ?? null, @@ -193,12 +199,12 @@ function useDefaultQueryDrafts(workspaceId?: string) { JSON.stringify(current) === JSON.stringify(submittedIssue) ? submittedIssue : current, ); setResetRequested(false); - toast({ description: "Default queries saved", variant: "success" }); + toast({ description: t("github:defaultQueriesSaved"), variant: "success" }); } catch { - toast({ description: "Failed to save default queries", variant: "error" }); + toast({ description: t("github:failedToSaveDefaultQueries"), variant: "error" }); throw new Error("Failed to save default queries"); } - }, [issueDraft, prDraft, reset, resetRequested, save, toast]); + }, [issueDraft, prDraft, reset, resetRequested, save, t, toast]); const handleReset = useCallback(() => { setPrDraft(toStored(BUILTIN_PR_PRESETS)); @@ -221,7 +227,7 @@ function useDefaultQueryDrafts(workspaceId?: string) { revision: JSON.stringify([prDraft, issueDraft]), isDirty: dirty, canSave: isReady, - invalidReason: isReady ? undefined : "Default queries are still loading.", + invalidReason: isReady ? undefined : t("github:defaultQueriesAreStillLoading"), save: handleSave, discard, }); @@ -247,12 +253,13 @@ function useDefaultQueryDrafts(workspaceId?: string) { } export function DefaultQueriesSection({ workspaceId }: { workspaceId?: string }) { + const { t } = useTranslation(); const drafts = useDefaultQueryDrafts(workspaceId); return (
} @@ -273,10 +280,10 @@ export function DefaultQueriesSection({ workspaceId }: { workspaceId?: string }) - Pull requests + {t("github:pullRequests")} - Issues + {t("github:issues")} @@ -284,7 +291,7 @@ export function DefaultQueriesSection({ workspaceId }: { workspaceId?: string }) presets={drafts.prDraft} baseline={drafts.prBaseline} onChange={drafts.setPrDraft} - addLabel="Add PR query" + addLabel={t("github:addPrQuery")} /> @@ -292,7 +299,7 @@ export function DefaultQueriesSection({ workspaceId }: { workspaceId?: string }) presets={drafts.issueDraft} baseline={drafts.issueBaseline} onChange={drafts.setIssueDraft} - addLabel="Add issue query" + addLabel={t("github:addIssueQuery")} /> diff --git a/apps/web/components/github/github-app-import-fields.tsx b/apps/web/components/github/github-app-import-fields.tsx index 5e1be5b407..5e9849acdb 100644 --- a/apps/web/components/github/github-app-import-fields.tsx +++ b/apps/web/components/github/github-app-import-fields.tsx @@ -105,7 +105,7 @@ export function GitHubAppImportSecretFields({ ))}
- +