{
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ handleClick();
+ }
+ }}
className={cn(
"relative overflow-hidden cursor-pointer rounded-lg border-2 border-dashed transition-all duration-300 min-h-[120px] flex flex-col items-center justify-center p-4 sm:p-6 gap-3",
isUploading
@@ -151,6 +168,7 @@ ComponentRegistry.register('file-upload',
size="sm"
className="h-6 w-6 p-0 hover:bg-destructive/10 hover:text-destructive"
onClick={() => handleRemoveFile(index)}
+ aria-label={t('fields.file.remove', { name: file.name })}
>
diff --git a/packages/fields/src/__tests__/iconButtonNames-13461.test.tsx b/packages/fields/src/__tests__/iconButtonNames-13461.test.tsx
new file mode 100644
index 0000000000..6e39f959ed
--- /dev/null
+++ b/packages/fields/src/__tests__/iconButtonNames-13461.test.tsx
@@ -0,0 +1,67 @@
+/**
+ * ObjectUI
+ * Copyright (c) 2024-present ObjectStack Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+/**
+ * objectui#13461 — the field widgets' icon-only buttons carry a name, read
+ * from the pack: the password reveal toggle (`fields.password.*`), the QR-code
+ * widget's copy and show / hide buttons (`fields.qrCode.*`) and the
+ * master-detail rows' open / remove buttons (`fields.masterDetail.*`, named
+ * after the row). The icon-only-button census (objectui#12143) proves a name
+ * attribute is present; this file proves which name a screen reader hears.
+ */
+import React from 'react';
+import { describe, it, expect, afterEach } from 'vitest';
+import { render, screen, cleanup, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { I18nProvider } from '@object-ui/i18n';
+import { en, zh } from '@object-ui/i18n/locales';
+import { PasswordField } from '../widgets/PasswordField';
+import { QRCodeField } from '../widgets/QRCodeField';
+import { MasterDetailField } from '../widgets/MasterDetailField';
+
+afterEach(cleanup);
+
+const inLocale = (language: 'en' | 'zh', node: React.ReactNode) => (
+
+ {node}
+
+);
+
+const noop = () => {};
+// The field metadata the three widgets read; the rest of the contract is irrelevant here.
+const field = { name: 'f', type: 'text' } as never;
+
+describe('field widget icon-only buttons are named from the pack (objectui#13461)', () => {
+ it('PasswordField: the reveal toggle says what it will do', () => {
+ render(inLocale('en',
));
+ const toggle = screen.getByRole('button', { name: en.fields.password.show });
+ fireEvent.click(toggle);
+ expect(toggle).toHaveAccessibleName(en.fields.password.hide);
+ });
+
+ it('QRCodeField: copy, and show / hide the code', () => {
+ render(inLocale('en',
));
+ expect(screen.getByRole('button', { name: en.fields.qrCode.copy })).toBeInTheDocument();
+ const toggle = screen.getByRole('button', { name: en.fields.qrCode.show });
+ fireEvent.click(toggle);
+ expect(toggle).toHaveAccessibleName(en.fields.qrCode.hide);
+ });
+
+ it('MasterDetailField: each row names its open and remove buttons after the row', () => {
+ render(inLocale('en',
));
+ const name = (pattern: string) => pattern.replace('{{name}}', 'Invoice 7');
+ expect(screen.getByRole('button', { name: name(en.fields.masterDetail.view) })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: name(en.fields.masterDetail.remove) })).toBeInTheDocument();
+ });
+
+ it('reads the zh pack under zh', () => {
+ expect(zh.fields.password.show).not.toBe(en.fields.password.show);
+ render(inLocale('zh',
));
+ expect(screen.getByRole('button', { name: zh.fields.password.show })).toBeInTheDocument();
+ });
+});
diff --git a/packages/fields/src/widgets/FileField.tsx b/packages/fields/src/widgets/FileField.tsx
index 5bea8782d7..dc07ce4e7e 100644
--- a/packages/fields/src/widgets/FileField.tsx
+++ b/packages/fields/src/widgets/FileField.tsx
@@ -431,6 +431,7 @@ export function FileField({ value, onChange, field, readonly, onUploadingChange,
handleRemove(idx);
}}
className="h-6 w-6 p-0"
+ aria-label={t('fields.file.remove', { name: file.name })}
>
diff --git a/packages/fields/src/widgets/MasterDetailField.tsx b/packages/fields/src/widgets/MasterDetailField.tsx
index fa6cca432f..9e5e691397 100644
--- a/packages/fields/src/widgets/MasterDetailField.tsx
+++ b/packages/fields/src/widgets/MasterDetailField.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import { Button, Badge, cn } from '@object-ui/components';
import { Plus, X, ExternalLink } from 'lucide-react';
+import { useObjectTranslation } from '@object-ui/i18n';
import { FieldWidgetComponentProps } from './types.js';
// TOMBSTONE (objectui#4811): this widget is NOT in `fieldWidgetMap`
@@ -39,6 +40,7 @@ export function MasterDetailField({
className,
...props
}: FieldWidgetComponentProps
) {
+ const { t } = useObjectTranslation();
const items = value || [];
const config = field;
@@ -77,6 +79,7 @@ export function MasterDetailField({
variant="ghost"
size="sm"
onClick={() => handleView(item)}
+ aria-label={t('fields.masterDetail.view', { name: item.label })}
>
@@ -108,6 +111,7 @@ export function MasterDetailField({
variant="ghost"
size="sm"
onClick={() => handleView(item)}
+ aria-label={t('fields.masterDetail.view', { name: item.label })}
>
@@ -117,6 +121,7 @@ export function MasterDetailField({
size="sm"
onClick={() => handleRemove(item.id)}
disabled={props.disabled}
+ aria-label={t('fields.masterDetail.remove', { name: item.label })}
>
diff --git a/packages/fields/src/widgets/PasswordField.tsx b/packages/fields/src/widgets/PasswordField.tsx
index ccc024acdf..95d8d47c02 100644
--- a/packages/fields/src/widgets/PasswordField.tsx
+++ b/packages/fields/src/widgets/PasswordField.tsx
@@ -3,6 +3,7 @@ import { Input, EmptyValue } from '@object-ui/components';
import { Button } from '@object-ui/components';
import { isEmptyValue } from '@object-ui/core';
import { Eye, EyeOff } from 'lucide-react';
+import { useObjectTranslation } from '@object-ui/i18n';
import { coerceToSafeValue } from '../coerceToSafeValue.js';
import { FieldWidgetComponentProps } from './types.js';
import { toDomProps } from './toDomProps.js';
@@ -64,6 +65,7 @@ function passwordAutoComplete(name: unknown): 'current-password' | 'new-password
* See {@link passwordAutoComplete} for how the token is picked.
*/
export function PasswordField({ value, onChange, field, readonly, className, ...props }: FieldWidgetComponentProps) {
+ const { t } = useObjectTranslation();
const [showPassword, setShowPassword] = useState(false);
const config = field;
@@ -93,6 +95,7 @@ export function PasswordField({ value, onChange, field, readonly, className, ...
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowPassword(!showPassword)}
tabIndex={-1}
+ aria-label={showPassword ? t('fields.password.hide') : t('fields.password.show')}
>
{showPassword ? (
diff --git a/packages/fields/src/widgets/QRCodeField.tsx b/packages/fields/src/widgets/QRCodeField.tsx
index 95d8130274..14169be3a6 100644
--- a/packages/fields/src/widgets/QRCodeField.tsx
+++ b/packages/fields/src/widgets/QRCodeField.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import { Input, Button, EmptyValue } from '@object-ui/components';
import { QrCode, Copy } from 'lucide-react';
+import { useObjectTranslation } from '@object-ui/i18n';
import { FieldWidgetComponentProps } from './types.js';
import { toDomProps } from './toDomProps.js';
@@ -9,6 +10,7 @@ import { toDomProps } from './toDomProps.js';
* Uses a simple SVG-based QR code generator
*/
export function QRCodeField({ value, onChange, field, readonly, error, ...props }: FieldWidgetComponentProps) {
+ const { t } = useObjectTranslation();
const [showQR, setShowQR] = React.useState(false);
const config = field;
@@ -68,6 +70,7 @@ export function QRCodeField({ value, onChange, field, readonly, error, ...props
variant="outline"
size="sm"
onClick={copyToClipboard}
+ aria-label={t('fields.qrCode.copy')}
>
@@ -76,6 +79,7 @@ export function QRCodeField({ value, onChange, field, readonly, error, ...props
variant="outline"
size="sm"
onClick={() => setShowQR(!showQR)}
+ aria-label={showQR ? t('fields.qrCode.hide') : t('fields.qrCode.show')}
>
diff --git a/packages/i18n/src/locales/ar.ts b/packages/i18n/src/locales/ar.ts
index defffab793..4c5ad99fd3 100644
--- a/packages/i18n/src/locales/ar.ts
+++ b/packages/i18n/src/locales/ar.ts
@@ -69,6 +69,9 @@ const ar = {
noRecords: 'لا توجد سجلات',
},
common: {
+ // objectui#13461 — a disclosure toggle's name (the tree-view node toggle).
+ expand: 'توسيع',
+ collapse: 'طي',
addToFavorites: "إضافة إلى المفضلة",
removeFromFavorites: "إزالة من المفضلة",
loading: "جارٍ التحميل…",
@@ -141,6 +144,9 @@ const ar = {
helpMultiValue: 'يُمرَّر إلى التدفق كمخرج للقرار. افصل بين القيم المتعددة بفواصل.',
},
resultDialog: {
+ // objectui#13461 — the secret block's reveal toggle, an icon-only button.
+ showValue: 'إظهار القيمة',
+ hideValue: 'إخفاء القيمة',
defaultTitle: 'احفظ هذه القيمة الآن',
acknowledge: 'لقد حفظتها',
copyAll: 'نسخ الكل',
@@ -246,6 +252,20 @@ const ar = {
},
},
fields: {
+ // objectui#13461 — names for icon-only buttons in the field widgets.
+ masterDetail: {
+ view: 'فتح {{name}}',
+ remove: 'إزالة {{name}}',
+ },
+ password: {
+ show: 'إظهار كلمة المرور',
+ hide: 'إخفاء كلمة المرور',
+ },
+ qrCode: {
+ copy: 'نسخ القيمة',
+ show: 'إظهار رمز QR',
+ hide: 'إخفاء رمز QR',
+ },
image: {
upload: "رفع صورة",
addMore: "إضافة المزيد من الصور",
@@ -3197,6 +3217,8 @@ const ar = {
forecastSoon: "عرض التوقعات قادم قريباً",
},
user: {
+ // objectui#13461 — the top bar's avatar button, which holds only the avatar.
+ accountMenu: 'قائمة الحساب',
profile: "الملف الشخصي",
settings: "الإعدادات",
logout: "تسجيل الخروج",
@@ -3657,6 +3679,9 @@ const ar = {
// pin advances, `chatbotToolLabels-locale-parity-7481` starts checking them
// against the registry instead of against its own hand-held list.
chatbot: {
+ // objectui#13461 — the composer's send button and the scroll-to-latest button.
+ sendMessage: 'إرسال الرسالة',
+ scrollToBottom: 'الانتقال إلى أحدث رسالة',
answer: {
showFull: 'عرض الإجابة كاملة',
collapse: 'طي',
diff --git a/packages/i18n/src/locales/de.ts b/packages/i18n/src/locales/de.ts
index 0778ab19a5..e11c89ed75 100644
--- a/packages/i18n/src/locales/de.ts
+++ b/packages/i18n/src/locales/de.ts
@@ -62,6 +62,9 @@ const de = {
noRecords: 'Keine Datensätze gefunden',
},
common: {
+ // objectui#13461 — a disclosure toggle's name (the tree-view node toggle).
+ expand: 'Erweitern',
+ collapse: 'Einklappen',
addToFavorites: "Zu Favoriten hinzufügen",
removeFromFavorites: "Aus Favoriten entfernen",
loading: "Wird geladen…",
@@ -127,6 +130,9 @@ const de = {
helpMultiValue: 'Wird dem Flow als Entscheidungsausgabe übergeben. Mehrere Werte durch Komma trennen.',
},
resultDialog: {
+ // objectui#13461 — the secret block's reveal toggle, an icon-only button.
+ showValue: 'Wert anzeigen',
+ hideValue: 'Wert verbergen',
defaultTitle: 'Diesen Wert jetzt speichern',
acknowledge: 'Ich habe ihn gespeichert',
copyAll: 'Alle kopieren',
@@ -232,6 +238,20 @@ const de = {
},
},
fields: {
+ // objectui#13461 — names for icon-only buttons in the field widgets.
+ masterDetail: {
+ view: '{{name}} öffnen',
+ remove: '{{name}} entfernen',
+ },
+ password: {
+ show: 'Passwort anzeigen',
+ hide: 'Passwort verbergen',
+ },
+ qrCode: {
+ copy: 'Wert kopieren',
+ show: 'QR-Code anzeigen',
+ hide: 'QR-Code verbergen',
+ },
image: {
upload: "Bild hochladen",
addMore: "Weitere Bilder hinzufügen",
@@ -2968,6 +2988,8 @@ const de = {
forecastSoon: "Prognoseansicht kommt bald",
},
user: {
+ // objectui#13461 — the top bar's avatar button, which holds only the avatar.
+ accountMenu: 'Kontomenü',
profile: "Profil",
settings: "Einstellungen",
logout: "Abmelden",
@@ -3392,6 +3414,9 @@ const de = {
// pin advances, `chatbotToolLabels-locale-parity-7481` starts checking them
// against the registry instead of against its own hand-held list.
chatbot: {
+ // objectui#13461 — the composer's send button and the scroll-to-latest button.
+ sendMessage: 'Nachricht senden',
+ scrollToBottom: 'Zur neuesten Nachricht scrollen',
answer: {
showFull: 'Ganze Antwort anzeigen',
collapse: 'Einklappen',
diff --git a/packages/i18n/src/locales/en.ts b/packages/i18n/src/locales/en.ts
index f050e925e1..72e4d1ac05 100644
--- a/packages/i18n/src/locales/en.ts
+++ b/packages/i18n/src/locales/en.ts
@@ -68,6 +68,9 @@ const en = {
retry: 'Retry',
},
common: {
+ // objectui#13461 — a disclosure toggle's name (the tree-view node toggle).
+ expand: 'Expand',
+ collapse: 'Collapse',
loading: 'Loading…',
save: 'Save',
// The SHARED "throw away the unsaved draft" verb, read by
@@ -144,6 +147,9 @@ const en = {
helpMultiValue: 'Handed to the flow as a decision output. Comma-separate multiple values.',
},
resultDialog: {
+ // objectui#13461 — the secret block's reveal toggle, an icon-only button.
+ showValue: 'Show value',
+ hideValue: 'Hide value',
defaultTitle: 'Save this value now',
acknowledge: 'I have saved this',
copyAll: 'Copy all',
@@ -348,6 +354,20 @@ const en = {
},
},
fields: {
+ // objectui#13461 — names for icon-only buttons in the field widgets.
+ masterDetail: {
+ view: 'Open {{name}}',
+ remove: 'Remove {{name}}',
+ },
+ password: {
+ show: 'Show password',
+ hide: 'Hide password',
+ },
+ qrCode: {
+ copy: 'Copy value',
+ show: 'Show QR code',
+ hide: 'Hide QR code',
+ },
relativeDate: {
overdue: 'Overdue {{count}}d',
overdue_one: 'Overdue {{count}}d',
@@ -3689,6 +3709,8 @@ const en = {
forecastSoon: 'Forecast view coming soon',
},
user: {
+ // objectui#13461 — the top bar's avatar button, which holds only the avatar.
+ accountMenu: 'Account menu',
profile: 'Profile',
settings: 'Settings',
logout: 'Log out',
@@ -4131,6 +4153,9 @@ const en = {
// pin advances, `chatbotToolLabels-locale-parity-7481` starts checking them
// against the registry instead of against its own hand-held list.
chatbot: {
+ // objectui#13461 — the composer's send button and the scroll-to-latest button.
+ sendMessage: 'Send message',
+ scrollToBottom: 'Scroll to the latest message',
answer: {
showFull: 'Show full answer',
collapse: 'Collapse',
diff --git a/packages/i18n/src/locales/es.ts b/packages/i18n/src/locales/es.ts
index 8e1554503b..4a18880144 100644
--- a/packages/i18n/src/locales/es.ts
+++ b/packages/i18n/src/locales/es.ts
@@ -63,6 +63,9 @@ const es = {
noRecords: 'No se encontraron registros',
},
common: {
+ // objectui#13461 — a disclosure toggle's name (the tree-view node toggle).
+ expand: 'Expandir',
+ collapse: 'Contraer',
addToFavorites: "Añadir a favoritos",
removeFromFavorites: "Quitar de favoritos",
loading: "Cargando…",
@@ -129,6 +132,9 @@ const es = {
helpMultiValue: 'Se transfiere al flujo como salida de la decisión. Separe varios valores con comas.',
},
resultDialog: {
+ // objectui#13461 — the secret block's reveal toggle, an icon-only button.
+ showValue: 'Mostrar valor',
+ hideValue: 'Ocultar valor',
defaultTitle: 'Guarda este valor ahora',
acknowledge: 'Lo he guardado',
copyAll: 'Copiar todo',
@@ -239,6 +245,20 @@ const es = {
},
},
fields: {
+ // objectui#13461 — names for icon-only buttons in the field widgets.
+ masterDetail: {
+ view: 'Abrir {{name}}',
+ remove: 'Quitar {{name}}',
+ },
+ password: {
+ show: 'Mostrar contraseña',
+ hide: 'Ocultar contraseña',
+ },
+ qrCode: {
+ copy: 'Copiar valor',
+ show: 'Mostrar código QR',
+ hide: 'Ocultar código QR',
+ },
image: {
upload: "Subir imagen",
addMore: "Añadir más imágenes",
@@ -3026,6 +3046,8 @@ const es = {
forecastSoon: "La vista de pronóstico llegará pronto",
},
user: {
+ // objectui#13461 — the top bar's avatar button, which holds only the avatar.
+ accountMenu: 'Menú de la cuenta',
profile: "Perfil",
settings: "Configuración",
logout: "Cerrar sesión",
@@ -3459,6 +3481,9 @@ const es = {
// pin advances, `chatbotToolLabels-locale-parity-7481` starts checking them
// against the registry instead of against its own hand-held list.
chatbot: {
+ // objectui#13461 — the composer's send button and the scroll-to-latest button.
+ sendMessage: 'Enviar mensaje',
+ scrollToBottom: 'Ir al último mensaje',
answer: {
showFull: 'Mostrar respuesta completa',
collapse: 'Contraer',
diff --git a/packages/i18n/src/locales/fr.ts b/packages/i18n/src/locales/fr.ts
index 23c1974f20..7743e8db24 100644
--- a/packages/i18n/src/locales/fr.ts
+++ b/packages/i18n/src/locales/fr.ts
@@ -63,6 +63,9 @@ const fr = {
noRecords: 'Aucun enregistrement trouvé',
},
common: {
+ // objectui#13461 — a disclosure toggle's name (the tree-view node toggle).
+ expand: 'Développer',
+ collapse: 'Réduire',
addToFavorites: "Ajouter aux favoris",
removeFromFavorites: "Retirer des favoris",
loading: "Chargement…",
@@ -129,6 +132,9 @@ const fr = {
helpMultiValue: 'Transmis au flux comme sortie de décision. Séparez les valeurs multiples par des virgules.',
},
resultDialog: {
+ // objectui#13461 — the secret block's reveal toggle, an icon-only button.
+ showValue: 'Afficher la valeur',
+ hideValue: 'Masquer la valeur',
defaultTitle: 'Enregistrez cette valeur maintenant',
acknowledge: 'Je l\'ai enregistrée',
copyAll: 'Tout copier',
@@ -234,6 +240,20 @@ const fr = {
},
},
fields: {
+ // objectui#13461 — names for icon-only buttons in the field widgets.
+ masterDetail: {
+ view: 'Ouvrir {{name}}',
+ remove: 'Supprimer {{name}}',
+ },
+ password: {
+ show: 'Afficher le mot de passe',
+ hide: 'Masquer le mot de passe',
+ },
+ qrCode: {
+ copy: 'Copier la valeur',
+ show: 'Afficher le code QR',
+ hide: 'Masquer le code QR',
+ },
image: {
upload: "Téléverser une image",
addMore: "Ajouter d'autres images",
@@ -3022,6 +3042,8 @@ const fr = {
forecastSoon: "La vue de prévision arrive bientôt",
},
user: {
+ // objectui#13461 — the top bar's avatar button, which holds only the avatar.
+ accountMenu: 'Menu du compte',
profile: "Profil",
settings: "Paramètres",
logout: "Se déconnecter",
@@ -3455,6 +3477,9 @@ const fr = {
// pin advances, `chatbotToolLabels-locale-parity-7481` starts checking them
// against the registry instead of against its own hand-held list.
chatbot: {
+ // objectui#13461 — the composer's send button and the scroll-to-latest button.
+ sendMessage: 'Envoyer le message',
+ scrollToBottom: 'Aller au dernier message',
answer: {
showFull: 'Afficher toute la réponse',
collapse: 'Réduire',
diff --git a/packages/i18n/src/locales/ja.ts b/packages/i18n/src/locales/ja.ts
index f9ebbfe3ab..15ba06815e 100644
--- a/packages/i18n/src/locales/ja.ts
+++ b/packages/i18n/src/locales/ja.ts
@@ -62,6 +62,9 @@ const ja = {
noRecords: 'レコードが見つかりません',
},
common: {
+ // objectui#13461 — a disclosure toggle's name (the tree-view node toggle).
+ expand: '展開',
+ collapse: '折りたたむ',
addToFavorites: "お気に入りに追加",
removeFromFavorites: "お気に入りから削除",
loading: "読み込み中…",
@@ -127,6 +130,9 @@ const ja = {
helpMultiValue: '決定の出力としてフローに渡されます。複数の値はカンマで区切ってください。',
},
resultDialog: {
+ // objectui#13461 — the secret block's reveal toggle, an icon-only button.
+ showValue: '値を表示',
+ hideValue: '値を非表示',
defaultTitle: 'この値を今すぐ保存してください',
acknowledge: '保存しました',
copyAll: 'すべてコピー',
@@ -232,6 +238,20 @@ const ja = {
},
},
fields: {
+ // objectui#13461 — names for icon-only buttons in the field widgets.
+ masterDetail: {
+ view: '{{name}} を開く',
+ remove: '{{name}} を削除',
+ },
+ password: {
+ show: 'パスワードを表示',
+ hide: 'パスワードを非表示',
+ },
+ qrCode: {
+ copy: '値をコピー',
+ show: 'QR コードを表示',
+ hide: 'QR コードを非表示',
+ },
image: {
upload: "画像をアップロード",
addMore: "画像を追加",
@@ -2970,6 +2990,8 @@ const ja = {
forecastSoon: "予測ビューは近日公開予定",
},
user: {
+ // objectui#13461 — the top bar's avatar button, which holds only the avatar.
+ accountMenu: 'アカウントメニュー',
profile: "プロフィール",
settings: "設定",
logout: "ログアウト",
@@ -3394,6 +3416,9 @@ const ja = {
// pin advances, `chatbotToolLabels-locale-parity-7481` starts checking them
// against the registry instead of against its own hand-held list.
chatbot: {
+ // objectui#13461 — the composer's send button and the scroll-to-latest button.
+ sendMessage: 'メッセージを送信',
+ scrollToBottom: '最新のメッセージまでスクロール',
answer: {
showFull: '全文を表示',
collapse: '折りたたむ',
diff --git a/packages/i18n/src/locales/ko.ts b/packages/i18n/src/locales/ko.ts
index df5db68bbe..96cfba1fb9 100644
--- a/packages/i18n/src/locales/ko.ts
+++ b/packages/i18n/src/locales/ko.ts
@@ -62,6 +62,9 @@ const ko = {
noRecords: '레코드를 찾을 수 없습니다',
},
common: {
+ // objectui#13461 — a disclosure toggle's name (the tree-view node toggle).
+ expand: '펼치기',
+ collapse: '접기',
addToFavorites: "즐겨찾기에 추가",
removeFromFavorites: "즐겨찾기에서 제거",
loading: "로딩 중…",
@@ -127,6 +130,9 @@ const ko = {
helpMultiValue: '결정 출력으로 플로우에 전달됩니다. 여러 값은 쉼표로 구분하세요.',
},
resultDialog: {
+ // objectui#13461 — the secret block's reveal toggle, an icon-only button.
+ showValue: '값 표시',
+ hideValue: '값 숨기기',
defaultTitle: '이 값을 지금 저장하세요',
acknowledge: '저장했습니다',
copyAll: '모두 복사',
@@ -232,6 +238,20 @@ const ko = {
},
},
fields: {
+ // objectui#13461 — names for icon-only buttons in the field widgets.
+ masterDetail: {
+ view: '{{name}} 열기',
+ remove: '{{name}} 제거',
+ },
+ password: {
+ show: '비밀번호 표시',
+ hide: '비밀번호 숨기기',
+ },
+ qrCode: {
+ copy: '값 복사',
+ show: 'QR 코드 표시',
+ hide: 'QR 코드 숨기기',
+ },
image: {
upload: "이미지 업로드",
addMore: "이미지 더 추가",
@@ -2967,6 +2987,8 @@ const ko = {
forecastSoon: "예측 보기가 곧 출시됩니다",
},
user: {
+ // objectui#13461 — the top bar's avatar button, which holds only the avatar.
+ accountMenu: '계정 메뉴',
profile: "프로필",
settings: "설정",
logout: "로그아웃",
@@ -3391,6 +3413,9 @@ const ko = {
// pin advances, `chatbotToolLabels-locale-parity-7481` starts checking them
// against the registry instead of against its own hand-held list.
chatbot: {
+ // objectui#13461 — the composer's send button and the scroll-to-latest button.
+ sendMessage: '메시지 보내기',
+ scrollToBottom: '최신 메시지로 스크롤',
answer: {
showFull: '전체 답변 보기',
collapse: '접기',
diff --git a/packages/i18n/src/locales/pt.ts b/packages/i18n/src/locales/pt.ts
index 6a0ea4d882..a6fa90e50f 100644
--- a/packages/i18n/src/locales/pt.ts
+++ b/packages/i18n/src/locales/pt.ts
@@ -63,6 +63,9 @@ const pt = {
noRecords: 'Nenhum registro encontrado',
},
common: {
+ // objectui#13461 — a disclosure toggle's name (the tree-view node toggle).
+ expand: 'Expandir',
+ collapse: 'Recolher',
addToFavorites: "Adicionar aos favoritos",
removeFromFavorites: "Remover dos favoritos",
loading: "Carregando…",
@@ -129,6 +132,9 @@ const pt = {
helpMultiValue: 'Entregue ao fluxo como saída da decisão. Separe vários valores por vírgulas.',
},
resultDialog: {
+ // objectui#13461 — the secret block's reveal toggle, an icon-only button.
+ showValue: 'Mostrar valor',
+ hideValue: 'Ocultar valor',
defaultTitle: 'Guarde este valor agora',
acknowledge: 'Eu o salvei',
copyAll: 'Copiar tudo',
@@ -234,6 +240,20 @@ const pt = {
},
},
fields: {
+ // objectui#13461 — names for icon-only buttons in the field widgets.
+ masterDetail: {
+ view: 'Abrir {{name}}',
+ remove: 'Remover {{name}}',
+ },
+ password: {
+ show: 'Mostrar senha',
+ hide: 'Ocultar senha',
+ },
+ qrCode: {
+ copy: 'Copiar valor',
+ show: 'Mostrar código QR',
+ hide: 'Ocultar código QR',
+ },
image: {
upload: "Enviar imagem",
addMore: "Adicionar mais imagens",
@@ -3021,6 +3041,8 @@ const pt = {
forecastSoon: "A exibição de previsão está chegando em breve",
},
user: {
+ // objectui#13461 — the top bar's avatar button, which holds only the avatar.
+ accountMenu: 'Menu da conta',
profile: "Perfil",
settings: "Configurações",
logout: "Sair",
@@ -3454,6 +3476,9 @@ const pt = {
// pin advances, `chatbotToolLabels-locale-parity-7481` starts checking them
// against the registry instead of against its own hand-held list.
chatbot: {
+ // objectui#13461 — the composer's send button and the scroll-to-latest button.
+ sendMessage: 'Enviar mensagem',
+ scrollToBottom: 'Ir para a mensagem mais recente',
answer: {
showFull: 'Mostrar resposta completa',
collapse: 'Recolher',
diff --git a/packages/i18n/src/locales/ru.ts b/packages/i18n/src/locales/ru.ts
index a17b83e721..9593633388 100644
--- a/packages/i18n/src/locales/ru.ts
+++ b/packages/i18n/src/locales/ru.ts
@@ -64,6 +64,9 @@ const ru = {
noRecords: 'Записи не найдены',
},
common: {
+ // objectui#13461 — a disclosure toggle's name (the tree-view node toggle).
+ expand: 'Развернуть',
+ collapse: 'Свернуть',
addToFavorites: "Добавить в избранное",
removeFromFavorites: "Удалить из избранного",
loading: "Загрузка…",
@@ -134,6 +137,9 @@ const ru = {
helpMultiValue: 'Передаётся в поток как результат решения. Несколько значений разделяйте запятыми.',
},
resultDialog: {
+ // objectui#13461 — the secret block's reveal toggle, an icon-only button.
+ showValue: 'Показать значение',
+ hideValue: 'Скрыть значение',
defaultTitle: 'Сохраните это значение сейчас',
acknowledge: 'Я сохранил(а) это',
copyAll: 'Копировать всё',
@@ -239,6 +245,20 @@ const ru = {
},
},
fields: {
+ // objectui#13461 — names for icon-only buttons in the field widgets.
+ masterDetail: {
+ view: 'Открыть {{name}}',
+ remove: 'Удалить {{name}}',
+ },
+ password: {
+ show: 'Показать пароль',
+ hide: 'Скрыть пароль',
+ },
+ qrCode: {
+ copy: 'Копировать значение',
+ show: 'Показать QR-код',
+ hide: 'Скрыть QR-код',
+ },
image: {
upload: "Загрузить изображение",
addMore: "Добавить ещё изображения",
@@ -3093,6 +3113,8 @@ const ru = {
forecastSoon: "Прогнозное представление скоро будет",
},
user: {
+ // objectui#13461 — the top bar's avatar button, which holds only the avatar.
+ accountMenu: 'Меню аккаунта',
profile: "Профиль",
settings: "Настройки",
logout: "Выйти",
@@ -3535,6 +3557,9 @@ const ru = {
// pin advances, `chatbotToolLabels-locale-parity-7481` starts checking them
// against the registry instead of against its own hand-held list.
chatbot: {
+ // objectui#13461 — the composer's send button and the scroll-to-latest button.
+ sendMessage: 'Отправить сообщение',
+ scrollToBottom: 'Перейти к последнему сообщению',
answer: {
showFull: 'Показать ответ полностью',
collapse: 'Свернуть',
diff --git a/packages/i18n/src/locales/zh.ts b/packages/i18n/src/locales/zh.ts
index b7289ebbec..307e7a4e63 100644
--- a/packages/i18n/src/locales/zh.ts
+++ b/packages/i18n/src/locales/zh.ts
@@ -67,6 +67,9 @@ const zh = {
retry: '重试',
},
common: {
+ // objectui#13461 — a disclosure toggle's name (the tree-view node toggle).
+ expand: '展开',
+ collapse: '收起',
loading: '加载中…',
save: '保存',
discard: '放弃',
@@ -132,6 +135,9 @@ const zh = {
helpMultiValue: '作为决策输出传递给流程。多个值请用逗号分隔。',
},
resultDialog: {
+ // objectui#13461 — the secret block's reveal toggle, an icon-only button.
+ showValue: '显示值',
+ hideValue: '隐藏值',
defaultTitle: '请立即保存此值',
acknowledge: '我已保存',
copyAll: '全部复制',
@@ -238,6 +244,20 @@ const zh = {
},
},
fields: {
+ // objectui#13461 — names for icon-only buttons in the field widgets.
+ masterDetail: {
+ view: '打开 {{name}}',
+ remove: '移除 {{name}}',
+ },
+ password: {
+ show: '显示密码',
+ hide: '隐藏密码',
+ },
+ qrCode: {
+ copy: '复制值',
+ show: '显示二维码',
+ hide: '隐藏二维码',
+ },
relativeDate: {
overdue: '逾期 {{count}} 天',
overdue_one: '逾期 {{count}} 天',
@@ -3175,6 +3195,8 @@ const zh = {
forecastSoon: '预测视图即将上线',
},
user: {
+ // objectui#13461 — the top bar's avatar button, which holds only the avatar.
+ accountMenu: '账户菜单',
profile: '个人资料',
settings: '设置',
logout: '注销',
@@ -3572,6 +3594,9 @@ const zh = {
// pin advances, `chatbotToolLabels-locale-parity-7481` starts checking them
// against the registry instead of against its own hand-held list.
chatbot: {
+ // objectui#13461 — the composer's send button and the scroll-to-latest button.
+ sendMessage: '发送消息',
+ scrollToBottom: '滚动到最新消息',
answer: {
showFull: '展开全文',
collapse: '收起',
diff --git a/packages/plugin-chatbot/src/elements/conversation.tsx b/packages/plugin-chatbot/src/elements/conversation.tsx
index 44994b2012..2de2ba5d97 100644
--- a/packages/plugin-chatbot/src/elements/conversation.tsx
+++ b/packages/plugin-chatbot/src/elements/conversation.tsx
@@ -8,6 +8,7 @@
import { Button } from "@object-ui/components";
import { cn } from "@object-ui/components";
+import { useObjectTranslation } from "@object-ui/i18n";
import { ArrowDownIcon } from "lucide-react";
import type { ComponentProps } from "react";
import { useCallback } from "react";
@@ -86,6 +87,7 @@ export const ConversationScrollButton = ({
className,
...props
}: ConversationScrollButtonProps) => {
+ const { t } = useObjectTranslation();
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
const handleScrollToBottom = useCallback(() => {
@@ -104,6 +106,8 @@ export const ConversationScrollButton = ({
size="icon"
type="button"
variant="outline"
+ // Before the spread, so a caller's own name still wins.
+ aria-label={t("chatbot.scrollToBottom")}
{...props}
>
diff --git a/packages/plugin-chatbot/src/index.tsx b/packages/plugin-chatbot/src/index.tsx
index 3f358296c7..e512569191 100644
--- a/packages/plugin-chatbot/src/index.tsx
+++ b/packages/plugin-chatbot/src/index.tsx
@@ -10,6 +10,7 @@ import * as React from "react"
import { cn } from "@object-ui/components"
import { Button, Input, ScrollArea, Avatar, AvatarFallback, AvatarImage } from "@object-ui/components"
import { Send } from "lucide-react"
+import { useObjectTranslation } from "@object-ui/i18n"
// The package's single chat message contract — the RUNTIME shape, named
// `ChatbotEnhancedMessage` so it no longer shares a name with
// `@object-ui/types`' authoring `ChatMessage` (objectui#6349, batch 5). See the
@@ -49,6 +50,7 @@ const Chatbot = React.forwardRef(
},
ref
) => {
+ const { t } = useObjectTranslation()
const [inputValue, setInputValue] = React.useState("")
const scrollRef = React.useRef(null)
const inputRef = React.useRef(null)
@@ -127,6 +129,7 @@ const Chatbot = React.forwardRef(
onClick={handleSend}
disabled={disabled || !inputValue.trim()}
size="icon"
+ aria-label={t("chatbot.sendMessage")}
>