Skip to content

Commit cef2838

Browse files
VeiaGGermanJablo
andauthored
feat(translations): add i18n translations for modular dashboards (#15004)
## What? Added internationalization (i18n) support for the Modular Dashboards feature, allowing dashboard widgets and UI elements to be translated into different languages. Also updated the automatic translation script to use the more efficient and cost-effective GPT-4.1 model instead of GPT-4. (i thinks this is must-have) ## Why? The Modular Dashboards feature previously had almost no translation support, making it inaccessible for non-English Payload CMS users. This was particularly important for international communities who need localized admin interfaces. Additionally, the translation automation script was using the expensive GPT-4 model, which quickly exhausted OpenAI API credits during translation work. ## How? Added new translation keys to all buttons & error inside Modular Dashboard. Translated all keys Translation Script Update: Changed model from gpt-4 to gpt-4.1 in the automatic translation script Maintained same translation quality while significantly reducing API costs (multiple times cheaper) ## Testing: Verified translations load correctly in different locales ( uk/en ) in `pnpm dev dashboard` ## Screenshots <img width="539" height="250" alt="image" src="https://github.com/user-attachments/assets/898b3618-e4ec-4f9e-9bb2-294eabd8aff5" /> <img width="685" height="215" alt="image" src="https://github.com/user-attachments/assets/c0024ca3-f31b-4e7d-b707-adfefa7ca190" /> <img width="830" height="399" alt="image" src="https://github.com/user-attachments/assets/ca6cd857-e0dd-47b0-8b36-6242396f2c73" /> <img width="792" height="311" alt="image" src="https://github.com/user-attachments/assets/19edcc53-648e-4ecd-8a48-8c03174c67bc" /> --------- Co-authored-by: German Jablonski <43938777+GermanJablo@users.noreply.github.com>
1 parent c5b2a91 commit cef2838

51 files changed

Lines changed: 548 additions & 22 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/next/src/views/Dashboard/Default/ModularDashboard/DashboardStepNav.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,22 +77,19 @@ export function DashboardBreadcrumbDropdown(props: {
7777
const { isEditing, onCancel, onEditClick, onResetLayout, onSaveChanges, widgetsDrawerSlug } =
7878
props
7979
const { t } = useTranslation()
80+
8081
if (isEditing) {
8182
return (
8283
<div className="dashboard-breadcrumb-dropdown__editing">
83-
<span>
84-
{t('general:editing')} {t('general:dashboard')}
85-
</span>
84+
<span>{t('dashboard:editingDashboard')}</span>
8685
<div className="dashboard-breadcrumb-dropdown__actions">
8786
<DrawerToggler className="drawer-toggler--unstyled" slug={widgetsDrawerSlug}>
8887
<Button buttonStyle="pill" el="span" size="small">
89-
{t('fields:addLabel', {
90-
label: '+',
91-
})}
88+
{t('dashboard:addButton')}
9289
</Button>
9390
</DrawerToggler>
9491
<Button buttonStyle="pill" onClick={onSaveChanges} size="small">
95-
{t('general:saveChanges')}
92+
{t('fields:saveChanges')}
9693
</Button>
9794
<Button buttonStyle="pill" onClick={onCancel} size="small">
9895
{t('general:cancel')}
@@ -103,8 +100,8 @@ export function DashboardBreadcrumbDropdown(props: {
103100
}
104101

105102
const options = [
106-
{ label: `${t('general:edit')} ${t('general:dashboard')}`, value: 'edit' },
107-
{ label: `${t('general:reset')} ${t('general:layout')}`, value: 'reset' },
103+
{ label: t('dashboard:editDashboard'), value: 'edit' },
104+
{ label: t('dashboard:resetLayout'), value: 'reset' },
108105
]
109106

110107
const handleChange = (selectedOption: Option | Option[]) => {

packages/next/src/views/Dashboard/Default/ModularDashboard/index.client.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,7 @@ export function ModularDashboardClient({
143143
>
144144
{currentLayout?.length === 0 && (
145145
<div className="modular-dashboard__empty">
146-
<p>
147-
There are no widgets on your dashboard. You can add them from the "Dashboard" menu
148-
located in the top bar.
149-
</p>
146+
<p>{t('dashboard:noItems')}</p>
150147
</div>
151148
)}
152149
{currentLayout?.map((widget, _index) => (

packages/next/src/views/Dashboard/Default/ModularDashboard/index.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,8 @@
275275
position: absolute;
276276
top: 100%;
277277
left: 0;
278-
min-width: 150px;
278+
min-width: max-content;
279+
width: max-content;
279280
z-index: 9999;
280281
border-radius: 4px;
281282
padding: 5px;

packages/next/src/views/Dashboard/Default/ModularDashboard/useDashboardLayout.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
useModal,
99
usePreferences,
1010
useServerFunctions,
11+
useTranslation,
1112
} from '@payloadcms/ui'
1213
import { PREFERENCE_KEYS } from 'payload/shared'
1314
import React, { useCallback, useEffect, useState } from 'react'
@@ -25,6 +26,7 @@ export function useDashboardLayout(initialLayout: WidgetInstanceClient[]) {
2526
const { openModal } = useModal()
2627
const cancelModalSlug = 'cancel-dashboard-changes'
2728
const { serverFunction } = useServerFunctions()
29+
const { t } = useTranslation()
2830

2931
// Sync state when initialLayout prop changes (e.g., when query params change and server component re-renders)
3032
useEffect(() => {
@@ -42,7 +44,7 @@ export function useDashboardLayout(initialLayout: WidgetInstanceClient[]) {
4244
await setLayoutPreference(layoutData)
4345
} catch {
4446
setIsEditing(true)
45-
toast.error('Failed to save layout')
47+
toast.error(t('error:failedToSaveLayout'))
4648
}
4749
}, [setLayoutPreference, currentLayout])
4850

@@ -58,7 +60,7 @@ export function useDashboardLayout(initialLayout: WidgetInstanceClient[]) {
5860
setCurrentLayout(result.layout)
5961
setIsEditing(false)
6062
} catch {
61-
toast.error('Failed to reset layout')
63+
toast.error(t('error:failedToResetLayout'))
6264
}
6365
}, [setLayoutPreference, serverFunction])
6466

@@ -211,9 +213,9 @@ export function useDashboardLayout(initialLayout: WidgetInstanceClient[]) {
211213
)
212214

213215
const cancelModal = React.createElement(ConfirmationModal, {
214-
body: 'You have unsaved changes to your dashboard layout. Are you sure you want to discard them?',
215-
confirmLabel: 'Discard',
216-
heading: 'Discard changes?',
216+
body: t('dashboard:discardMessage'),
217+
confirmLabel: t('dashboard:discardConfirmLabel'),
218+
heading: t('dashboard:discardTitle'),
217219
modalSlug: cancelModalSlug,
218220
onConfirm: performCancel,
219221
})

packages/translations/src/clientKeys.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ export const clientTranslationKeys = createClientTranslationKeys([
6565
'dashboard:addWidget',
6666
'dashboard:deleteWidget',
6767
'dashboard:searchWidgets',
68+
'dashboard:editDashboard',
69+
'dashboard:editingDashboard',
70+
'dashboard:resetLayout',
71+
'dashboard:addButton',
72+
'dashboard:discardConfirmLabel',
73+
'dashboard:discardMessage',
74+
'dashboard:discardTitle',
75+
'dashboard:noItems',
6876

6977
'error:autosaving',
7078
'error:correctInvalidFields',
@@ -98,6 +106,8 @@ export const clientTranslationKeys = createClientTranslationKeys([
98106
'error:revertingDocument',
99107
'error:problemUploadingFile',
100108
'error:restoringTitle',
109+
'error:failedToSaveLayout',
110+
'error:failedToResetLayout',
101111

102112
'fields:addLabel',
103113
'fields:addLink',

packages/translations/src/languages/ar.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,18 @@ export const arTranslations: DefaultTranslationsObject = {
8080
'إن لم تطلب هذا ، يرجى تجاهل هذا البريد الإلكتروني وستبقى كلمة مرورك ذاتها بدون تغيير.',
8181
},
8282
dashboard: {
83+
addButton: 'اضف +',
8384
addWidget: 'أضف الواجهة البيانية',
8485
deleteWidget: 'حذف الودجت {{id}}',
86+
discardConfirmLabel: 'تجاهل',
87+
discardMessage:
88+
'لديك تغييرات غير محفوظة في تخطيط لوحة التحكم الخاصة بك. هل أنت متأكد أنك تريد التخلي عنها؟',
89+
discardTitle: 'هل تريد التخلص من التغييرات؟',
90+
editDashboard: 'تعديل لوحة التحكم',
91+
editingDashboard: 'لوحة تحرير',
92+
noItems:
93+
'لا توجد أدوات على لوحة التحكم الخاصة بك. يمكنك إضافتها من القائمة "لوحة التحكم" الموجودة في الشريط العلوي.',
94+
resetLayout: 'إعادة تعيين التخطيط',
8595
searchWidgets: 'ابحث عن الأدوات...',
8696
},
8797
error: {
@@ -94,6 +104,8 @@ export const arTranslations: DefaultTranslationsObject = {
94104
documentNotFound:
95105
'لم يتم العثور على المستند بالمعرف {{id}}. قد يكون قد تم حذفه أو لم يكن موجودًا أصلاً ، أو قد لا يكون لديك الوصول إليه.',
96106
emailOrPasswordIncorrect: 'البريد الإلكتروني أو كلمة المرور المقدمة غير صحيحة.',
107+
failedToResetLayout: 'فشل في إعادة تعيين التخطيط.',
108+
failedToSaveLayout: 'فشل في حفظ التخطيط.',
97109
followingFieldsInvalid_one: 'الحقل التالي غير صالح:',
98110
followingFieldsInvalid_other: 'الحقول التالية غير صالحة:',
99111
incorrectCollection: 'مجموعة غير صحيحة',

packages/translations/src/languages/az.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,18 @@ export const azTranslations: DefaultTranslationsObject = {
8080
'Əgər siz bunu tələb etməmisinizsə, lütfən, bu e-poçtu nəzərə almayın və şifrəniz dəyişilməz qalacaq.',
8181
},
8282
dashboard: {
83+
addButton: 'Əlavə et +',
8384
addWidget: 'Vidjet əlavə et',
8485
deleteWidget: 'Vidgeti silin {{id}}',
86+
discardConfirmLabel: 'Atılma',
87+
discardMessage:
88+
'Sizin panel düzenlemənizdə saxlanmamış dəyişikliklər var. Onları ləğvetmək istədiyinizə əminsinizmi?',
89+
discardTitle: 'Dəyişiklikləri ləğv edin?',
90+
editDashboard: 'İdarə Panelini Redaktə Et',
91+
editingDashboard: 'Redaktə Paneli',
92+
noItems:
93+
'Sizin panelinizdə heç bir vidjet yoxdur. Siz onları yuxarıdaki çubuqda yerləşən "Panel" menyusundan əlavə edə bilərsiniz.',
94+
resetLayout: 'Düzəni sıfırlama',
8595
searchWidgets: 'Widgetləri axtarın...',
8696
},
8797
error: {
@@ -94,6 +104,8 @@ export const azTranslations: DefaultTranslationsObject = {
94104
documentNotFound:
95105
'{{id}} ID-li sənəd tapılmadı. Bu, onun silinmiş və ya heç vaxt mövcud olmamış ola bilər və ya sizin ona giriş hüququnuz olmayabilir.',
96106
emailOrPasswordIncorrect: 'Təqdim olunan e-poçt və ya şifrə yanlışdır.',
107+
failedToResetLayout: 'Düzeni sıfırlamaq uğursuz oldu.',
108+
failedToSaveLayout: 'Dizaynı saxlamaq alınmadı.',
97109
followingFieldsInvalid_one: 'Aşağıdakı sahə yanlışdır:',
98110
followingFieldsInvalid_other: 'Aşağıdaki sahələr yanlışdır:',
99111
incorrectCollection: 'Yanlış Kolleksiya',

packages/translations/src/languages/bg.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,18 @@ export const bgTranslations: DefaultTranslationsObject = {
8080
'Ако не си заявил това, игнорирай този имейл и паролата ти ще остане непроменена.',
8181
},
8282
dashboard: {
83+
addButton: 'Добавете +',
8384
addWidget: 'Добави джаджа',
8485
deleteWidget: 'Изтрийте джаджа {{id}}',
86+
discardConfirmLabel: 'Отхвърляне',
87+
discardMessage:
88+
'Имате незапазени промени в оформлението на таблото си. Сигурни ли сте, че искате да ги отхвърлите?',
89+
discardTitle: 'Отхвърляне на промените?',
90+
editDashboard: 'Редактирай таблото',
91+
editingDashboard: 'Редактиране на таблото',
92+
noItems:
93+
'Няма джаджи на таблото ви. Можете да ги добавите от менюто "Табло", което е разположено в горната лента.',
94+
resetLayout: 'Рестартирай Оформлението',
8595
searchWidgets: 'Търсене на джаджи...',
8696
},
8797
error: {
@@ -94,6 +104,8 @@ export const bgTranslations: DefaultTranslationsObject = {
94104
documentNotFound:
95105
'Документът с ID {{id}} не можа да бъде намерен. Възможно е да е бил изтрит или никога да не е съществувал или може би нямате достъп до него.',
96106
emailOrPasswordIncorrect: 'Имейлът или паролата не са правилни.',
107+
failedToResetLayout: 'Неуспешно нулиране на оформлението.',
108+
failedToSaveLayout: 'Неуспешно запазване на оформлението.',
97109
followingFieldsInvalid_one: 'Следното поле е некоректно:',
98110
followingFieldsInvalid_other: 'Следните полета са некоректни:',
99111
incorrectCollection: 'Грешна колекция',

packages/translations/src/languages/bnBd.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,18 @@ export const bnBdTranslations: DefaultTranslationsObject = {
8080
'আপনি যদি এটি অনুরোধ না করে থাকেন, তাহলে এই ইমেইলটি উপেক্ষা করুন এবং আপনার পাসওয়ার্ড অপরিবর্তিত থাকবে।',
8181
},
8282
dashboard: {
83+
addButton: 'যোগ করুন +',
8384
addWidget: 'উইজেট যোগ করুন',
8485
deleteWidget: 'উইজেট মুছে ফেলুন {{id}}',
86+
discardConfirmLabel: 'বাতিল করুন',
87+
discardMessage:
88+
'আপনার ড্যাশবোর্ড লেআউটে আপনার অসংরক্ষিত পরিবর্তনগুলি রয়েছে। আপনি কি নিশ্চিত যে আপনি তা বাতিল করতে চান?',
89+
discardTitle: 'পরিবর্তনগুলি বাতিল করবেন?',
90+
editDashboard: 'ড্যাশবোর্ড সম্পাদনা করুন',
91+
editingDashboard: 'ড্যাশবোর্ড সম্পাদনা করুন',
92+
noItems:
93+
'আপনার ড্যাশবোর্ডে কোনো উইজেট নেই। আপনি তা শীর্ষ বারে অবস্থিত "ড্যাশবোর্ড" মেনু থেকে যোগ করতে পারেন।',
94+
resetLayout: 'লেআউট রিসেট করুন',
8595
searchWidgets: 'উইজেটগুলি অনুসন্ধান করুন...',
8696
},
8797
error: {
@@ -94,6 +104,8 @@ export const bnBdTranslations: DefaultTranslationsObject = {
94104
documentNotFound:
95105
'আইডি {{id}} এর সাথে সম্পর্কিত ডকুমেন্টটি পাওয়া যাচ্ছে না। এটি মোছা হয়েছে বা কখনই না থাকতে পারে, অথবা আপনার এর প্রবেশাধিকার না থ',
96106
emailOrPasswordIncorrect: 'প্রদত্ত ইমেইল বা পাসওয়ার্ড ভুল।',
107+
failedToResetLayout: 'লেআউট রিসেট করতে ব্যর্থ হয়েছে।',
108+
failedToSaveLayout: 'লেআউট সংরক্ষণ করতে ব্যর্থ হয়েছে।',
97109
followingFieldsInvalid_one: 'নিম্নলিখিত ক্ষেত্রটি অবৈধ:',
98110
followingFieldsInvalid_other: 'নিম্নলিখিত ক্ষেত্রগুলি অবৈধ:',
99111
incorrectCollection: 'ভুল সংগ্রহ',

packages/translations/src/languages/bnIn.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,18 @@ export const bnInTranslations: DefaultTranslationsObject = {
8080
'আপনি যদি এটি অনুরোধ না করে থাকেন, তাহলে এই ইমেইলটি উপেক্ষা করুন এবং আপনার পাসওয়ার্ড অপরিবর্তিত থাকবে।',
8181
},
8282
dashboard: {
83+
addButton: 'যোগ করুন +',
8384
addWidget: 'উইজেট যোগ করুন',
8485
deleteWidget: 'উইজেট মুছুন {{id}}',
86+
discardConfirmLabel: 'বাতিল করুন',
87+
discardMessage:
88+
'আপনার ড্যাশবোর্ড লেআউটে আপনার অসংরক্ষিত পরিবর্তনগুলি রয়েছে। আপনি কি নিশ্চিত যে আপনি তাদের খারিজ করতে চান?',
89+
discardTitle: 'পরিবর্তন বাতিল করবেন?',
90+
editDashboard: 'ড্যাশবোর্ড সম্পাদনা করুন',
91+
editingDashboard: 'ড্যাশবোর্ড সম্পাদনা করা',
92+
noItems:
93+
'আপনার ড্যাশবোর্ডে কোন উইজেট নেই। আপনি তাদেরকে শীর্ষ বারে অবস্থিত "ড্যাশবোর্ড" মেনু থেকে যোগ করতে পারেন।',
94+
resetLayout: 'লেআউট পুনরায় সেট করুন',
8595
searchWidgets: 'উইজেট অনুসন্ধান করুন...',
8696
},
8797
error: {
@@ -94,6 +104,8 @@ export const bnInTranslations: DefaultTranslationsObject = {
94104
documentNotFound:
95105
'ID সহ {{id}} ডকুমেন্টটি পাওয়া যায়নি। এটি মুছে ফেলা হতে পারে বা কখনই ছিল না, অথবা আপনার এটির অ্যাক্সেস নেই।',
96106
emailOrPasswordIncorrect: 'প্রদত্ত ইমেইল বা পাসওয়ার্ড ভুল।',
107+
failedToResetLayout: 'লেআউট পুনরায় সেট করতে ব্যর্থ হয়েছে।',
108+
failedToSaveLayout: 'লেআউট সংরক্ষণ করতে ব্যর্থ।',
97109
followingFieldsInvalid_one: 'নিম্নলিখিত ক্ষেত্রটি অবৈধ:',
98110
followingFieldsInvalid_other: 'নিম্নলিখিত ক্ষেত্রগুলি অবৈধ:',
99111
incorrectCollection: 'ভুল সংগ্রহ',

0 commit comments

Comments
 (0)