Skip to content

Commit 912c916

Browse files
Nexeon-Aiclaude
andcommitted
Add Arabic language with full RTL support
Register Arabic ('ar') as a supported UI language and implement full right-to-left layout mirroring — the platform's first RTL language. Language registration: - Add 'ar' to enabled-language lists (dev/prod + desktop platform.ts, branding.json) and the settings language dropdown (العربية). - Add the "Arabic" label string to the ui plugin + all locale files. Translation: - Add ar.json for all 66 plugins (~4000 strings, Modern Standard Arabic), preserving keys and ICU placeholders. Validated against en parity with the platform's intl-messageformat parser. - Extend the official lang parity test (testUtils) to cover 'ar'. RTL: - Theme.svelte sets document dir=rtl for RTL languages, live on switch. - scripts/rtl-codemod.mjs rewrites 971 physical CSS declarations across 292 files to logical properties (margin/padding/border/inset/text-align/ float) — identical to physical in LTR, so no LTR regression. - Chevron mirrors horizontal carets; _rtl.scss keeps code/mono LTR. Typography: - Bundle IBM Plex Sans Arabic (matches the IBM Plex UI family) and append it to the font stack so Latin and Arabic glyphs resolve per-glyph. Tooling: - scripts/translate-arabic.mjs (Claude API) for reproducible re-translation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ce3e346 commit 912c916

386 files changed

Lines changed: 5643 additions & 975 deletions

File tree

Some content is hidden

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

desktop/src/ui/platform.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ export async function configurePlatform (onWorkbenchConnect?: () => Promise<void
412412
const languages =
413413
myBranding.languages !== undefined && myBranding.languages !== ''
414414
? myBranding.languages.split(',').map((l) => l.trim())
415-
: ['en', 'ru', 'es', 'pt', 'pt-br', 'zh', 'fr', 'cs', 'it', 'de', 'ja', 'ko', 'tr']
415+
: ['en', 'ru', 'es', 'pt', 'pt-br', 'zh', 'fr', 'cs', 'it', 'de', 'ja', 'ko', 'tr', 'ar']
416416

417417
setMetadata(uiPlugin.metadata.Languages, languages)
418418

dev/prod/public/branding.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"huly.local:8080": {
33
"title": "Huly",
4-
"languages": "en,ru,pt,pt-br,es,zh,fr,de,ja,ko,tr",
4+
"languages": "en,ru,pt,pt-br,es,zh,fr,de,ja,ko,tr,ar",
55
"defaultLanguage": "en",
66
"defaultApplication": "tracker",
77
"defaultSpace": "tracker:project:DefaultProject",
@@ -30,7 +30,7 @@
3030
},
3131
"huly.local:8087": {
3232
"title": "Huly",
33-
"languages": "en,ru,pt,pt-br,es,zh,fr,de,ja,ko,tr",
33+
"languages": "en,ru,pt,pt-br,es,zh,fr,de,ja,ko,tr,ar",
3434
"defaultLanguage": "en",
3535
"defaultApplication": "tracker",
3636
"defaultSpace": "tracker:project:DefaultProject",

dev/prod/src/platform.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ export async function configurePlatform() {
545545

546546
const languages = myBranding.languages
547547
? myBranding.languages.split(',').map((l) => l.trim())
548-
: ['en', 'ru', 'es', 'pt', 'pt-br', 'zh', 'fr', 'cs', 'it', 'de', 'ja', 'ko', 'tr']
548+
: ['en', 'ru', 'es', 'pt', 'pt-br', 'zh', 'fr', 'cs', 'it', 'de', 'ja', 'ko', 'tr', 'ar']
549549

550550
setMetadata(uiPlugin.metadata.Languages, languages)
551551

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
{
2+
"string": {
3+
"Id": "المعرّف",
4+
"Space": "المساحة",
5+
"Spaces": "المساحات",
6+
"SpacesDescription": "إدارة نوع جميع المساحات",
7+
"TypedSpace": "مساحة من نوع محدد",
8+
"SpaceType": "نوع المساحة",
9+
"Modified": "مُعدَّل",
10+
"ModifiedDate": "تاريخ التعديل",
11+
"ModifiedBy": "عُدِّل بواسطة",
12+
"Class": "الفئة",
13+
"AttachedTo": "مُرفق بـ",
14+
"AttachedToClass": "مُرفق بالفئة",
15+
"Name": "الاسم",
16+
"Description": "الوصف",
17+
"ShortDescription": "وصف موجز",
18+
"Descriptor": "الواصف",
19+
"TargetClass": "الفئة المستهدفة",
20+
"Role": "الدور",
21+
"Roles": "الأدوار",
22+
"Private": "خاص",
23+
"Archived": "مؤرشف",
24+
"ClassLabel": "النوع",
25+
"ClassPropertyLabel": "التسمية",
26+
"String": "نص",
27+
"Markup": "ترميز",
28+
"Number": "رقم",
29+
"Boolean": "مربع اختيار",
30+
"Timestamp": "طابع زمني",
31+
"Date": "تاريخ",
32+
"IntlString": "IntlString",
33+
"Ref": "مرجع",
34+
"Collection": "مجموعة",
35+
"Array": "اختيار متعدد",
36+
"Enum": "اختيار",
37+
"Members": "الأعضاء",
38+
"Hyperlink": "URL",
39+
"MarkupBlobRef": "تعاوني",
40+
"Object": "كائن",
41+
"System": "النظام",
42+
"CreatedBy": "أنشئ بواسطة",
43+
"CreatedDate": "تاريخ الإنشاء",
44+
"Status": "الحالة",
45+
"StatusCategory": "فئة الحالة",
46+
"Account": "الحساب",
47+
"Rank": "الترتيب",
48+
"Owners": "المالكون",
49+
"Permission": "الإذن",
50+
"CreateObject": "إنشاء كائن",
51+
"UpdateObject": "تحديث كائن",
52+
"DeleteObject": "حذف كائن",
53+
"ForbidDeleteObject": "منع حذف الكائن",
54+
"UpdateSpace": "تحديث المساحة",
55+
"ArchiveSpace": "أرشفة المساحة",
56+
"CreateObjectDescription": "يمنح المستخدمين القدرة على إنشاء كائنات في المساحة",
57+
"UpdateObjectDescription": "يمنح المستخدمين القدرة على تحديث الكائنات في المساحة",
58+
"DeleteObjectDescription": "يمنح المستخدمين القدرة على حذف الكائنات في المساحة",
59+
"ForbidDeleteObjectDescription": "منع المستخدمين من حذف الكائنات في المساحة",
60+
"UpdateSpaceDescription": "يمنح المستخدمين القدرة على تحديث المساحة",
61+
"ArchiveSpaceDescription": "يمنح المستخدمين القدرة على أرشفة المساحة",
62+
"AutoJoin": "انضمام تلقائي",
63+
"AutoJoinDescr": "إضافة الموظفين الجدد تلقائيًا إلى هذه المساحة",
64+
"AutoJoinGuests": "انضمام الضيوف تلقائيًا",
65+
"AutoJoinGuestsDescr": "عند التفعيل، يُضاف ضيوف مساحة العمل إلى هذه المساحة عند التنشيط",
66+
"RBAC": "التحكم في الوصول المستند إلى الأدوار",
67+
"RBACDescr": "اشتراط الوصول المستند إلى الأدوار لتنفيذ الإجراءات في هذه المساحة",
68+
"BlobSize": "الحجم",
69+
"BlobContentType": "نوع المحتوى",
70+
"Relation": "علاقة",
71+
"Relations": "العلاقات",
72+
"AddRelation": "إضافة علاقة",
73+
"PersonId": "الشخص",
74+
"AccountId": "الحساب",
75+
"Version": "الإصدار"
76+
}
77+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"status": {
3+
"LoadingPlugin": "جارٍ تحميل الإضافة {plugin}...",
4+
"UnknownError": "خطأ غير معروف: {message}",
5+
"InvalidId": "معرّف غير صالح: {id}",
6+
"BadRequest": "طلب غير صالح",
7+
"Forbidden": "محظور",
8+
"Conflict": "تعارض",
9+
"ExpiredLink": "انتهت صلاحية رابط الدعوة هذا",
10+
"Unauthorized": "غير مصرَّح به",
11+
"UnknownMethod": "طريقة غير معروفة: {method}",
12+
"InternalServerError": "خطأ داخلي في الخادم",
13+
"MaintenanceWarning": "الصيانة مجدولة خلال",
14+
"MaintenanceWarningTime": "{time, plural, =1 {أقل من دقيقة} other {# دقيقة}}",
15+
"AccountNotFound": "لم يُعثر على الحساب أو أن بيانات الاعتماد المقدَّمة غير صحيحة",
16+
"AccountNotConfirmed": "لم يتم تأكيد الحساب",
17+
"WorkspaceNotFound": "لم يُعثر على مساحة العمل",
18+
"AccountAlreadyExists": "الحساب موجود بالفعل",
19+
"WorkspaceRateLimit": "الخادم مشغول، يُرجى الانتظار قليلاً والمحاولة مرة أخرى",
20+
"AccountAlreadyConfirmed": "تم تأكيد الحساب بالفعل",
21+
"WorkspaceAlreadyExists": "مساحة العمل موجودة بالفعل",
22+
"InvalidOtp": "رمز غير صالح",
23+
"PasswordLoginLocked": "تم قفل تسجيل الدخول بكلمة المرور بسبب كثرة المحاولات الفاشلة. يُرجى استخدام طريقة تسجيل الدخول برمز OTP لإلغاء قفل حسابك.",
24+
"InviteNotFound": "لم يُعثر على دعوة بالبريد الإلكتروني:{email}.",
25+
"WorkspaceLimitReached": "لقد وصلت إلى الحد الأقصى لمساحات العمل. يُرجى التواصل معنا...",
26+
"ReadOnlyAccount": "حساب ضيف تجريبي مجهول",
27+
"SystemAccount": "حساب النظام",
28+
"SocialIdAlreadyExists": "Social ID موجود بالفعل"
29+
}
30+
}

foundations/core/packages/platform/src/testUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ function makeLocaleMatcher (target: object): object {
1010
)
1111
}
1212

13-
const langs = ['en', 'ru']
13+
const langs = ['en', 'ru', 'ar']
1414

1515
/**
1616
* @public

packages/presentation/lang/ar.json

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"string": {
3+
"Create": "إنشاء",
4+
"Cancel": "إلغاء",
5+
"Ok": "موافق",
6+
"Save": "حفظ",
7+
"Saved": "تم الحفظ...",
8+
"Download": "تنزيل",
9+
"DownloadOriginal": "تنزيل النسخة الأصلية",
10+
"Delete": "حذف",
11+
"Close": "إغلاق",
12+
"NotSelected": "غير محدد",
13+
"Deselect": "إلغاء التحديد",
14+
"Archived": "(مؤرشف)",
15+
"AddSocialLinks": "إضافة روابط التواصل الاجتماعي",
16+
"EditSocialLinks": "تحرير روابط التواصل الاجتماعي",
17+
"Change": "تغيير",
18+
"Remove": "إزالة",
19+
"Search": "بحث...",
20+
"Spaces": "المساحات",
21+
"NumberSpaces": "{count, plural, =0 {في} =1 {في مكان واحد} other {في # أماكن}}",
22+
"InThis": "في هذه {space}",
23+
"NoMatchesInThis": "لا توجد مطابقات في هذه {space}",
24+
"NoMatchesFound": "لم يتم العثور على مطابقات",
25+
"NotInThis": "ليس في هذه {space}",
26+
"Match": "مطابقة",
27+
"Add": "إضافة",
28+
"Edit": "تحرير",
29+
"DocumentPreview": "معاينة",
30+
"MakePrivate": "جعله خاصاً",
31+
"MakePrivateDescription": "يمكن للأعضاء فقط رؤيته",
32+
"Created": "تم الإنشاء",
33+
"Selected": "محدد",
34+
"NoResults": "لا توجد نتائج لعرضها",
35+
"Next": "التالي",
36+
"FailedToPreview": "فشلت المعاينة",
37+
"ContentType": "نوع المحتوى",
38+
"ContentTypeNotSupported": "المعاينة غير متاحة لنوع المحتوى هذا",
39+
"StartDrawing": "خربشة فوقه",
40+
"DrawingHistory": "سجل الخربشة",
41+
"ColorAdd": "إضافة لون مُعد مسبقاً",
42+
"ColorRemove": "إزالة لون مُعد مسبقاً",
43+
"ColorReset": "إعادة تعيين الألوان المُعدة مسبقاً",
44+
"Copy": "نسخ",
45+
"DocumentUrlCopied": "تم النسخ!",
46+
"CopyLink": "نسخ الرابط",
47+
"AccessDenied": "العنصر غير موجود أو ليس لديك إذن بالوصول إليه.",
48+
"UnableToFollowMention": "تعذّر فتح العنصر المُشار إليه",
49+
"Undo": "تراجع",
50+
"Redo": "إعادة",
51+
"ClearCanvas": "مسح لوحة الرسم",
52+
"PenTool": "قلم",
53+
"EraserTool": "ممحاة",
54+
"PanTool": "تحريك",
55+
"TextTool": "نص",
56+
"LineTool": "خط",
57+
"RectangleTool": "مستطيل",
58+
"EllipseTool": "بيضاوي",
59+
"PaletteManagementMenu": "إدارة الألوان المُعدة مسبقاً",
60+
"BetaVersion": "نسخة تجريبية"
61+
},
62+
"status": {
63+
"FileTooLarge": "الملف كبير جداً"
64+
}
65+
}

packages/presentation/src/components/DrawingBoardToolbar.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,9 +351,9 @@
351351
bottom: 100%;
352352
353353
&.inside {
354-
left: 0.5rem;
354+
inset-inline-start: 0.5rem;
355355
top: 0.5rem;
356-
right: auto;
356+
inset-inline-end: auto;
357357
bottom: unset;
358358
display: inline-flex;
359359
flex-wrap: wrap;
@@ -393,7 +393,7 @@
393393
.tool-indicator {
394394
position: absolute;
395395
bottom: -0.125rem;
396-
right: -0.125rem;
396+
inset-inline-end: -0.125rem;
397397
width: 0;
398398
height: 0;
399399
border-style: solid;

packages/presentation/src/components/Image.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
border-radius: inherit;
8585
position: absolute;
8686
top: 0;
87-
left: 0;
87+
inset-inline-start: 0;
8888
width: 100%;
8989
height: 100%;
9090
display: flex;

packages/presentation/src/components/PluginConfigurationCard.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,6 @@
142142
opacity: 0.75;
143143
font-size: 0.8125rem;
144144
line-height: 1.4;
145-
padding-left: calc(1.25rem + 0.625rem);
145+
padding-inline-start: calc(1.25rem + 0.625rem);
146146
}
147147
</style>

0 commit comments

Comments
 (0)