Skip to content

Commit 5bd4f41

Browse files
committed
Merge branch 'main' of https://github.com/Windshiftapp/core into feat/russian-locale
# Conflicts: # frontend/src/lib/jira-import/SystemImportPage.svelte # frontend/src/lib/layout/DashboardCustomizationSidebar.svelte # frontend/src/lib/locales/ar/index.js # frontend/src/lib/locales/de/index.js # frontend/src/lib/locales/en/index.js # frontend/src/lib/locales/es/index.js # frontend/src/lib/locales/pt-BR/index.js # frontend/src/lib/locales/zh-CN/index.js # frontend/src/lib/pages/Homepage.svelte # frontend/src/lib/services/dashboardWidgetRegistry.js # frontend/src/lib/stores/homepageStore.svelte.js # frontend/src/lib/stores/i18n-utils.spec.js # frontend/src/lib/widgets/dashboard/AssignedToMeWidget.svelte # frontend/src/lib/widgets/dashboard/DailyBriefingWidget.svelte # frontend/src/lib/widgets/dashboard/DashboardItemRow.svelte # frontend/src/lib/widgets/dashboard/PersonalTasksWidget.svelte # frontend/src/lib/widgets/dashboard/QuickAccessWidget.svelte # frontend/src/lib/widgets/dashboard/RecentWorkspacesWidget.svelte # frontend/src/lib/widgets/dashboard/UpcomingMilestonesWidget.svelte # frontend/src/lib/widgets/dashboard/WatchedItemsWidget.svelte # frontend/src/lib/widgets/dashboard/WhatsNewWidget.svelte # frontend/src/lib/widgets/dashboard/YourActivityWidget.svelte
2 parents 2e27c17 + e9dfb75 commit 5bd4f41

55 files changed

Lines changed: 1841 additions & 336 deletions

Some content is hidden

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

BUILD.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ make help
2828
```bash
2929
# Build frontend
3030
cd frontend
31-
npm install
31+
npm ci
3232
npm run build
3333
cd ..
3434

@@ -127,12 +127,12 @@ make build-windows-arm64
127127
## Build Requirements
128128

129129
### For Server + Frontend
130-
- **Go 1.25+** - Backend compilation
131-
- **Node.js 18+** - Frontend build
132-
- **npm** - Package management
130+
- **Go 1.27.0** - Exact version declared in `go.mod`
131+
- **Node.js 24.18.0** - Exact version pinned in `.nvmrc` and `frontend/package.json`
132+
- **npm 11.16.0** - Exact version pinned in `frontend/package.json`
133133

134134
### For WS Client Only
135-
- **Go 1.25+** - Client compilation only
135+
- **Go 1.27.0** - Exact version declared in `go.mod`
136136

137137
### For Cross-Compilation (release.sh)
138138
- **Docker + Buildx** - Multi-arch Docker images
@@ -171,10 +171,11 @@ Production builds use these Go build flags:
171171

172172
### Frontend Build Issues
173173
```bash
174-
# Clear npm cache and reinstall
174+
# Reinstall exactly from the lockfile
175175
cd frontend
176-
rm -rf node_modules package-lock.json
177-
npm install
176+
rm -rf node_modules
177+
npm cache verify
178+
npm ci
178179
npm run build
179180
```
180181

frontend/scripts/check-i18n.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,15 @@ async function loadLocaleFiles(localeCode) {
166166
.filter((f) => f.endsWith('.js') && f !== 'index.js')
167167
.sort((a, b) => {
168168
const rank = (file) =>
169-
file === 'review.js' ? 3 : file === 'quality.js' ? 2 : file === 'supplemental.js' ? 1 : 0;
169+
file === 'review.js'
170+
? 3
171+
: file === 'quality.js'
172+
? 2
173+
: file === 'supplemental.js'
174+
? 1
175+
: file === 'dashboard.js'
176+
? 0.5
177+
: 0;
170178
return rank(a) - rank(b) || a.localeCompare(b);
171179
});
172180

@@ -206,6 +214,12 @@ async function extractSourceKeys() {
206214
for (const match of matches) {
207215
keys.add(match[1]);
208216
}
217+
const metadataMatches = content.matchAll(
218+
/\b(?:name|description|label|title|subtitle)Key:\s*['"]([a-zA-Z][a-zA-Z0-9_.]+)['"]/g
219+
);
220+
for (const match of metadataMatches) {
221+
keys.add(match[1]);
222+
}
209223
}
210224

211225
return keys;

frontend/src/lib/components/SafeMarkdown.svelte

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,24 @@
6060
padding-inline-start: 1.5rem;
6161
}
6262
63+
.safe-markdown :global(ul) {
64+
list-style-type: disc;
65+
}
66+
67+
.safe-markdown :global(ol) {
68+
list-style-type: decimal;
69+
}
70+
71+
.safe-markdown :global(li) {
72+
display: list-item;
73+
margin-block: 0.25em;
74+
}
75+
76+
.safe-markdown :global(li:has(> input[type='checkbox']:first-child)),
77+
.safe-markdown :global(li:has(> p:first-child > input[type='checkbox']:first-child)) {
78+
list-style-type: none;
79+
}
80+
6381
.safe-markdown :global(a) {
6482
color: var(--ds-text-link);
6583
text-decoration: underline;

frontend/src/lib/jira-import/JiraImportStore.svelte.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ export const jiraImport = {
685685
importState.phase = status.phase || 'running';
686686
importState.progress = status.progress;
687687

688-
if (status.status === 'completed') {
688+
if (status.status === 'completed' || status.status === 'completed_with_errors') {
689689
importState.result = status;
690690
markWizardStepComplete('import');
691691
return; // Stop polling

frontend/src/lib/jira-import/JiraImportWizard.svelte

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,7 +1334,7 @@
13341334
>
13351335
<div class="flex items-center justify-between gap-3">
13361336
<span class="font-mono text-xs" style="color: var(--ds-text);">{conflict.job_id}</span>
1337-
<span class="text-xs capitalize" style="color: var(--ds-text-subtle);">{conflict.status?.replace('_', ' ')}</span>
1337+
<span class="text-xs capitalize" style="color: var(--ds-text-subtle);">{conflict.status?.replaceAll('_', ' ')}</span>
13381338
</div>
13391339
{#if conflict.project_keys?.length}
13401340
<p class="text-xs mt-1" style="color: var(--ds-text-subtle);">Projects: {conflict.project_keys.join(', ')}</p>
@@ -1382,9 +1382,15 @@
13821382
data-total-attachments={importData.progress?.total_attachments || 0}
13831383
data-imported-attachments={importData.progress?.imported_attachments || 0}
13841384
>
1385-
<Check class="w-16 h-16 mx-auto" style="color: var(--ds-text-success);" />
1385+
{#if importData.result.status === 'completed_with_errors'}
1386+
<AlertCircle class="w-16 h-16 mx-auto" style="color: var(--ds-text-warning);" />
1387+
{:else}
1388+
<Check class="w-16 h-16 mx-auto" style="color: var(--ds-text-success);" />
1389+
{/if}
13861390
<p class="text-lg font-medium mt-4" style="color: var(--ds-text);">
1387-
{t('jiraImport.import.complete')}
1391+
{importData.result.status === 'completed_with_errors'
1392+
? t('jiraImport.import.completeWithErrors')
1393+
: t('jiraImport.import.complete')}
13881394
</p>
13891395
<p class="text-sm mt-2" style="color: var(--ds-text-subtle);">
13901396
{t('jiraImport.import.success', { count: importData.progress?.imported_issues || 0 })}
@@ -1410,6 +1416,17 @@
14101416
{importData.progress.failed_tests === 1 ? ' case failed' : ' cases failed'} to import.
14111417
</p>
14121418
{/if}
1419+
{#if importData.progress?.failed_projects > 0}
1420+
<p class="text-sm mt-1 text-amber-600" data-testid="jira-import-failed-project-count">
1421+
{importData.progress.failed_projects} Jira
1422+
{importData.progress.failed_projects === 1 ? ' project failed' : ' projects failed'} to import.
1423+
</p>
1424+
{/if}
1425+
{#if importData.progress?.failed_links > 0}
1426+
<p class="text-sm mt-1 text-amber-600" data-testid="jira-import-failed-link-count">
1427+
Jira issue links could not be imported.
1428+
</p>
1429+
{/if}
14131430
</div>
14141431
{:else if importData.isImporting || importData.jobId}
14151432
<div class="text-center" data-testid="jira-import-progress">

frontend/src/lib/jira-import/SystemImportPage.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
function getStatusColor(status) {
8484
switch (status) {
8585
case 'completed': return 'var(--ds-text-success)';
86+
case 'completed_with_errors': return 'var(--ds-text-warning)';
8687
case 'running': return 'var(--ds-text-accent-blue)';
8788
case 'failed': return 'var(--ds-text-danger)';
8889
case 'data_deleted': return 'var(--ds-text-subtle)';
@@ -94,6 +95,7 @@
9495
function getStatusIcon(status) {
9596
switch (status) {
9697
case 'completed': return CheckCircle;
98+
case 'completed_with_errors': return AlertTriangle;
9799
case 'running': return Loader;
98100
case 'failed': return XCircle;
99101
case 'data_deleted': return Trash2;

frontend/src/lib/layout/DashboardCustomizationSidebar.svelte

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,26 @@
2929
let categories = $derived([
3030
{
3131
id: dashboardWidgetCategories.ACTIVITY,
32-
name: t('dashboard.customization.activity'),
32+
nameKey: 'dashboard.customization.activity.name',
3333
icon: Clock,
34-
description: t('dashboard.customization.activityDescription'),
34+
descriptionKey: 'dashboard.customization.activity.description',
3535
},
3636
{
3737
id: dashboardWidgetCategories.WORK,
38-
name: t('dashboard.customization.work'),
38+
nameKey: 'dashboard.customization.work.name',
3939
icon: CheckSquare,
40-
description: t('dashboard.customization.workDescription'),
40+
descriptionKey: 'dashboard.customization.work.description',
4141
},
4242
{
4343
id: dashboardWidgetCategories.NAVIGATION,
44-
name: t('dashboard.customization.navigation'),
44+
nameKey: 'dashboard.customization.navigation.name',
4545
icon: Compass,
46-
description: t('dashboard.customization.navigationDescription'),
46+
descriptionKey: 'dashboard.customization.navigation.description',
4747
},
4848
]);
4949
5050
let currentWidgets = $derived(getDashboardWidgetsByCategory(activeCategory));
51+
let currentCategory = $derived(categories.find((category) => category.id === activeCategory));
5152
5253
function handleKeydown(event) {
5354
if (event.key === 'Escape' && isOpen) {
@@ -90,7 +91,8 @@
9091
if (!isActive) e.currentTarget.style.cssText = 'color: var(--ds-text-subtle);';
9192
}}
9293
onclick={() => (activeCategory = category.id)}
93-
title={category.name}
94+
title={t(category.nameKey)}
95+
aria-label={t(category.nameKey)}
9496
>
9597
<CategoryIcon class="w-5 h-5" />
9698
</button>
@@ -100,8 +102,8 @@
100102
<!-- Right content panel -->
101103
<div class="w-96 flex flex-col" style="background-color: var(--ds-surface-raised);">
102104
<ModalHeader
103-
title={categories.find((c) => c.id === activeCategory)?.name || t('dashboard.customization.widgets')}
104-
subtitle={categories.find((c) => c.id === activeCategory)?.description || ''}
105+
title={currentCategory ? t(currentCategory.nameKey) : t('dashboard.customization.widgets')}
106+
subtitle={currentCategory ? t(currentCategory.descriptionKey) : ''}
105107
onClose={() => (isOpen = false)}
106108
/>
107109

@@ -137,7 +139,7 @@
137139
class="text-xs px-2 py-0.5 rounded"
138140
style="background-color: var(--ds-background-neutral); color: var(--ds-text-subtle);"
139141
>
140-
{categories.find((category) => category.id === widget.category)?.name || widget.category}
142+
{t(`dashboard.customization.${widget.category}.name`)}
141143
</span>
142144
<span class="text-xs" style="color: var(--ds-text-subtlest);">
143145
{t('widgets.defaultWidth', {
@@ -163,7 +165,8 @@
163165
style="background-color: var(--ds-background-neutral); border: 1px solid var(--ds-border);"
164166
>
165167
<p class="text-xs" style="color: var(--ds-text);">
166-
<strong>{t('dashboard.customization.tipTitle')}:</strong> {t('dashboard.customization.tip')}
168+
<strong>{t('dashboard.customization.tipLabel')}:</strong>
169+
{t('dashboard.customization.tip')}
167170
</p>
168171
</div>
169172
</div>
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
export default {
2+
dashboard: {
3+
salutation: {
4+
withName: '{salutation}، {name}!',
5+
withoutName: '{salutation}!',
6+
},
7+
sections: {
8+
yourDay: {
9+
title: 'يومك',
10+
subtitle: 'نظرة سريعة على ما يحتاج إلى انتباهك',
11+
},
12+
work: {
13+
title: 'العمل',
14+
subtitle: 'قائمتك الشخصية والعناصر المسندة إليك',
15+
},
16+
workspaces: {
17+
title: 'مساحات العمل',
18+
subtitle: 'تابع من حيث توقفت',
19+
},
20+
},
21+
widgetCatalog: {
22+
dailyBriefing: {
23+
name: 'الموجز اليومي',
24+
description: 'ملخص مولد بالذكاء الاصطناعي لما يهمك اليوم',
25+
},
26+
yourActivity: {
27+
name: 'نشاطك',
28+
description: 'العناصر التي عرضتها أو عدلتها أو علقت عليها مؤخراً',
29+
},
30+
whatsNew: {
31+
name: 'ما الجديد',
32+
description: 'أحدث الإشعارات والتحديثات غير المقروءة',
33+
},
34+
personalTasks: {
35+
name: 'المهام الشخصية',
36+
description: 'عناصر من قائمة مهامك الشخصية',
37+
},
38+
savedSearch: {
39+
name: 'بحث محفوظ',
40+
description: 'عرض عناصر العمل من مجموعة محفوظة',
41+
},
42+
assignedToMe: {
43+
name: 'مسند إليّ',
44+
description: 'العناصر المفتوحة المسندة إليك في جميع مساحات العمل',
45+
},
46+
watchedItems: {
47+
name: 'العناصر المراقبة',
48+
description: 'العناصر التي تتابعها',
49+
},
50+
upcomingMilestones: {
51+
name: 'المعالم القادمة',
52+
description: 'المعالم ذات التواريخ المستهدفة القريبة',
53+
},
54+
recentWorkspaces: {
55+
name: 'مساحات العمل الأخيرة',
56+
description: 'مساحات العمل التي زرتها مؤخراً',
57+
},
58+
quickAccess: {
59+
name: 'الوصول السريع',
60+
description: 'روابط سريعة إلى مساحات العمل المتاحة لك',
61+
},
62+
},
63+
customization: {
64+
widgets: 'الأدوات',
65+
activity: {
66+
name: 'النشاط',
67+
description: 'الموجزات وتدفقات النشاط والإشعارات',
68+
},
69+
work: {
70+
name: 'العمل',
71+
description: 'العناصر والمعالم والمهام المسندة إليك',
72+
},
73+
navigation: {
74+
name: 'التنقل',
75+
description: 'وصول سريع إلى مساحات العمل',
76+
},
77+
tipLabel: 'تلميح',
78+
tip: 'اسحب الأدوات من هنا إلى أي قسم في لوحة المعلومات.',
79+
},
80+
editor: {
81+
newSection: 'قسم جديد',
82+
deleteSectionConfirm: 'هل تريد حذف هذا القسم؟ ستتم إزالة جميع الأدوات الموجودة فيه.',
83+
doneEditing: 'إنهاء التحرير',
84+
customize: 'تخصيص',
85+
editModeDescription: 'وضع التحرير: أضف الأقسام والأدوات أو أعد تسميتها أو ترتيبها أو احذفها',
86+
addSection: 'إضافة قسم',
87+
sectionLabel: 'قسم لوحة المعلومات',
88+
sectionTitlePlaceholder: 'عنوان القسم',
89+
sectionSubtitlePlaceholder: 'العنوان الفرعي (اختياري)',
90+
renameSection: 'إعادة تسمية القسم',
91+
deleteSection: 'حذف القسم',
92+
unknownWidgetType: 'نوع أداة غير معروف: {type}',
93+
noWidgets: 'لا توجد أدوات في هذا القسم بعد',
94+
addWidgetsHint: 'اختر تخصيص لإضافة أدوات',
95+
noSections: 'لم يتم إعداد أي أقسام',
96+
addSectionsHint: 'اختر تحرير لإضافة أقسام إلى لوحة المعلومات',
97+
},
98+
states: {
99+
assignedLoadError: 'تعذر تحميل العناصر المسندة إليك',
100+
assignedEmpty: 'لا يوجد شيء مسند إليك حالياً',
101+
personalTasksLoadError: 'تعذر تحميل مهامك الشخصية',
102+
personalTasksEmpty: 'قائمة مهامك الشخصية فارغة',
103+
dailyBriefingUnavailable: 'موجزك اليومي غير متاح حالياً. يعتمد على تكامل للذكاء الاصطناعي. إذا أعددت تكاملاً للتو، فحاول مرة أخرى بعد قليل.',
104+
updatedAt: 'تم التحديث في {time}',
105+
priorityLabel: 'الأولوية: {priority}',
106+
noWorkspaces: 'لا توجد مساحات عمل بعد',
107+
createWorkspace: 'إنشاء مساحة',
108+
workspaceAvatarAlt: 'الصورة الرمزية لـ {name}',
109+
visited: 'تمت الزيارة {time}',
110+
noUpcomingMilestones: 'لا توجد معالم قادمة',
111+
milestoneProgress: 'تم إنجاز {done} من {total}',
112+
daysOverdue: 'متأخر {days} أيام',
113+
daysLeft: 'متبقي {days} أيام',
114+
watchedItemsEmpty: 'أنت لا تراقب أي عناصر',
115+
workspaceWithId: 'مساحة العمل {id}',
116+
},
117+
},
118+
};

frontend/src/lib/locales/ar/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import pages from './pages.js';
2222
import teams from './teams.js';
2323
import supplemental from './supplemental.js';
2424
import adminOperations from './adminOperations.js';
25+
import dashboard from './dashboard.js';
2526

2627
export default createLocale({
2728
common,
@@ -42,4 +43,5 @@ export default createLocale({
4243
teams,
4344
supplemental,
4445
adminOperations,
46+
dashboard,
4547
});

frontend/src/lib/locales/ar/misc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export default {
106106
importing: 'جاري الاستيراد...',
107107
starting: 'جاري بدء الاستيراد...',
108108
complete: 'اكتمل الاستيراد!',
109+
completeWithErrors: 'اكتمل الاستيراد مع وجود أخطاء',
109110
success: 'تم استيراد {count} عنصر بنجاح.',
110111
failed: 'فشل استيراد {count} عنصر.',
111112
ready: 'جاهز للاستيراد',

0 commit comments

Comments
 (0)