Skip to content

Commit 3e59c60

Browse files
os-zhuangclaude
andauthored
feat(i18n): translate action description (closes the last gap of action i18n) (#1778)
Action labels, confirm text, success messages, and parameter label/placeholder/helpText/option-label already resolve through i18n. The one piece the convention was missing is the action's own `description` (the explanatory line shown in the param dialog / sheet / drawer header), which flowed through to useActionModal raw. - Add an `actionDescription` resolver to useObjectLabel (mirrors actionSuccess; convention `objects.{object}._actions.{action}.description`, falls back to globalActions, then the metadata literal). - Wire it where the action descriptor is built before the dialog opens — useConsoleActionRuntime + RecordDetailView — so the dialog receives a localized description (identical mechanism to the already-working actionParamText wiring). Note: no shipped action currently sets a top-level `description`, so this is a forward-looking capability with no visible change today; it closes the "description not translatable" half of the issue. Verified by build + the full app-shell (546) and i18n (120) suites; the test mock gains actionDescription so the runtime tests stay green. Closes the i18n-action gap tracked in cloud#73. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7cda1ad commit 3e59c60

4 files changed

Lines changed: 22 additions & 4 deletions

File tree

packages/app-shell/src/hooks/__tests__/useConsoleActionRuntime.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ vi.mock('@object-ui/i18n', () => ({
3333
fieldOptionLabel: (_o: any, _f: any, _v: any, l: any) => l,
3434
actionParamText: (_o: any, _a: any, _p: any, _attr: any, fallback: any) => fallback,
3535
actionParamOptionLabel: (_o: any, _a: any, _p: any, _v: any, fallback: any) => fallback,
36+
actionDescription: (_o: any, _a: any, fallback: any) => fallback,
3637
}),
3738
}));
3839

packages/app-shell/src/hooks/useConsoleActionRuntime.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export function useConsoleActionRuntime(opts: ConsoleActionRuntimeOptions): Cons
8787
const { dataSource, objects, objectName, onRefresh } = opts;
8888
const navigate = useNavigate();
8989
const { user, activeOrganization } = useAuth();
90-
const { fieldLabel, fieldOptionLabel, actionParamText, actionParamOptionLabel } = useObjectLabel();
90+
const { fieldLabel, fieldOptionLabel, actionParamText, actionParamOptionLabel, actionDescription } = useObjectLabel();
9191

9292
const objectDef = useMemo(
9393
() => (objectName ? objects?.find((o: any) => o.name === objectName) : undefined),
@@ -152,7 +152,7 @@ export function useConsoleActionRuntime(opts: ConsoleActionRuntimeOptions): Cons
152152
open: true,
153153
params: localized,
154154
title: action?.label || action?.title,
155-
description: action?.description,
155+
description: actionDescription(objForI18n, action?.name, action?.description),
156156
resolve,
157157
});
158158
});

packages/app-shell/src/views/RecordDetailView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export function RecordDetailView({ dataSource, objects, onEdit, objectNameOverri
119119
const location = useLocation();
120120
const originFrom = (location.state as any)?.from as { pathname?: string; label?: string } | undefined;
121121
const { t } = useObjectTranslation();
122-
const { objectLabel, viewLabel: _vLabel, sectionLabel, actionLabel, actionConfirm, actionSuccess, actionParamText, actionParamOptionLabel, fieldLabel, fieldOptionLabel } = useObjectLabel();
122+
const { objectLabel, viewLabel: _vLabel, sectionLabel, actionLabel, actionConfirm, actionSuccess, actionParamText, actionParamOptionLabel, actionDescription, fieldLabel, fieldOptionLabel } = useObjectLabel();
123123
const { isFavorite, toggleFavorite, refreshLabel: refreshFavoriteLabel } = useFavorites();
124124
const { addRecentItem } = useRecentItems();
125125
const [isLoading, setIsLoading] = useState(true);
@@ -341,7 +341,7 @@ export function RecordDetailView({ dataSource, objects, onEdit, objectNameOverri
341341
params: localized,
342342
// Title the dialog as the action rather than the generic "Action parameters".
343343
title: action?.label || action?.title,
344-
description: action?.description,
344+
description: actionDescription(objForI18n, action?.name, action?.description),
345345
resolve,
346346
});
347347
});

packages/i18n/src/useObjectLabel.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,23 @@ export function useObjectLabel() {
385385
return resolved || undefined;
386386
},
387387

388+
/**
389+
* Resolve translated action description (the explanatory line shown in the
390+
* action's param dialog / sheet / drawer header).
391+
* Convention: `{ns}.objects.{objectName}._actions.{actionName}.description`.
392+
* Falls back to `{ns}.globalActions.{actionName}.description`, then the
393+
* metadata's literal string; undefined when nothing resolves.
394+
*/
395+
actionDescription: (objectName: string | undefined, actionName: string | undefined, fallback?: string) => {
396+
const fb = fallback ?? '';
397+
if (!actionName) return fb || undefined;
398+
const suffixes = objectName
399+
? objectSuffixes(objectName, `_actions.${actionName}.description`)
400+
: `globalActions.${actionName}.description`;
401+
const resolved = resolve(suffixes, fb);
402+
return resolved || undefined;
403+
},
404+
388405
/**
389406
* Resolve translated action-PARAMETER text (label / placeholder / helpText).
390407
* Convention: `{ns}.objects.{objectName}._actions.{actionName}.params.{paramName}.{attr}`.

0 commit comments

Comments
 (0)