i18n-dashboard 0.18.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/common/FormatImportModal.vue +15 -3
- package/src/components/common/FormatSnippet.vue +8 -8
- package/src/components/common/WidgetConfigModal.vue +19 -19
- package/src/components/common/WidgetPicker.vue +21 -21
- package/src/components/common/widgets/ActivityWidget.vue +29 -29
- package/src/components/common/widgets/CustomIframeWidget.vue +18 -17
- package/src/components/common/widgets/LanguagesCoverageWidget.vue +45 -39
- package/src/components/common/widgets/ProjectsWidget.vue +30 -30
- package/src/components/common/widgets/ReviewWidget.vue +39 -39
- package/src/components/common/widgets/StatWidget.vue +19 -19
- package/src/components/dashboard/WidgetGrid.vue +61 -61
- package/src/components/project/WidgetGrid.vue +61 -61
- package/src/layouts/auth.vue +7 -6
- package/src/layouts/default.vue +157 -154
- package/src/pages/admin/customization.vue +124 -124
- package/src/pages/admin/security.vue +37 -35
- package/src/pages/admin/smtp.vue +80 -78
- package/src/pages/login.vue +39 -39
- package/src/pages/onboarding.vue +247 -246
- package/src/pages/projects/[id]/formats/datetime.vue +98 -94
- package/src/pages/projects/[id]/formats/modifiers.vue +80 -76
- package/src/pages/projects/[id]/formats/number.vue +113 -109
- package/src/pages/projects/[id]/languages.vue +209 -202
- package/src/pages/projects/[id]/review.vue +73 -69
- package/src/pages/projects/[id]/settings.vue +158 -158
- package/src/pages/projects/[id]/translations/[keyId].vue +227 -222
- package/src/pages/projects/[id]/translations/index.vue +108 -108
- package/src/pages/projects/[id]/users.vue +162 -158
- package/src/pages/reset-password.vue +44 -44
- package/src/pages/users/[id]/profile.vue +159 -155
package/package.json
CHANGED
|
@@ -77,7 +77,11 @@
|
|
|
77
77
|
class="flex items-center gap-2.5 px-3 py-2 rounded-lg bg-gray-50 dark:bg-gray-800 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
78
78
|
@click="f.checked = !f.checked"
|
|
79
79
|
>
|
|
80
|
-
<u-checkbox
|
|
80
|
+
<u-checkbox
|
|
81
|
+
:model-value="f.checked"
|
|
82
|
+
@click.stop
|
|
83
|
+
@update:model-value="f.checked = Boolean($event)"
|
|
84
|
+
/>
|
|
81
85
|
<span class="text-xs font-mono bg-gray-200 dark:bg-gray-700 px-1.5 py-0.5 rounded text-gray-600 dark:text-gray-300">{{ f.locale }}</span>
|
|
82
86
|
<span class="text-sm font-medium text-gray-800 dark:text-gray-200 flex-1">{{ f.name }}</span>
|
|
83
87
|
<code class="text-xs text-gray-400 truncate max-w-40">{{ formatOptionsPreview(f.options) }}</code>
|
|
@@ -97,7 +101,11 @@
|
|
|
97
101
|
class="flex items-center gap-2.5 px-3 py-2 rounded-lg bg-gray-50 dark:bg-gray-800 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
98
102
|
@click="f.checked = !f.checked"
|
|
99
103
|
>
|
|
100
|
-
<u-checkbox
|
|
104
|
+
<u-checkbox
|
|
105
|
+
:model-value="f.checked"
|
|
106
|
+
@click.stop
|
|
107
|
+
@update:model-value="f.checked = Boolean($event)"
|
|
108
|
+
/>
|
|
101
109
|
<span class="text-xs font-mono bg-gray-200 dark:bg-gray-700 px-1.5 py-0.5 rounded text-gray-600 dark:text-gray-300">{{ f.locale }}</span>
|
|
102
110
|
<span class="text-sm font-medium text-gray-800 dark:text-gray-200 flex-1">{{ f.name }}</span>
|
|
103
111
|
<code class="text-xs text-gray-400 truncate max-w-40">{{ formatOptionsPreview(f.options) }}</code>
|
|
@@ -117,7 +125,11 @@
|
|
|
117
125
|
class="flex items-center gap-2.5 px-3 py-2 rounded-lg bg-gray-50 dark:bg-gray-800 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
118
126
|
@click="m.checked = !m.checked"
|
|
119
127
|
>
|
|
120
|
-
<u-checkbox
|
|
128
|
+
<u-checkbox
|
|
129
|
+
:model-value="m.checked"
|
|
130
|
+
@click.stop
|
|
131
|
+
@update:model-value="m.checked = Boolean($event)"
|
|
132
|
+
/>
|
|
121
133
|
<span class="text-sm font-medium text-gray-800 dark:text-gray-200 flex-1">{{ m.name }}</span>
|
|
122
134
|
<code class="text-xs text-gray-400 truncate max-w-48 font-mono">{{ m.body }}</code>
|
|
123
135
|
</div>
|
|
@@ -4,20 +4,20 @@
|
|
|
4
4
|
<div class="flex items-center justify-between gap-2">
|
|
5
5
|
<div class="flex items-center gap-2">
|
|
6
6
|
<u-icon
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
class="text-green-500"
|
|
8
|
+
name="i-heroicons-code-bracket"
|
|
9
9
|
/>
|
|
10
10
|
<h2 class="font-semibold text-gray-900 dark:text-white">
|
|
11
11
|
{{ t('formats.snippet_title', 'Integration snippet') }}
|
|
12
12
|
</h2>
|
|
13
13
|
</div>
|
|
14
14
|
<u-button
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
:label="copied ? t('common.copied', 'Copied!') : t('common.copy', 'Copy')"
|
|
16
|
+
:color="copied ? 'success' : 'neutral'"
|
|
17
|
+
:icon="copied ? 'i-heroicons-check' : 'i-heroicons-clipboard'"
|
|
18
|
+
size="xs"
|
|
19
|
+
variant="ghost"
|
|
20
|
+
@click="copy"
|
|
21
21
|
/>
|
|
22
22
|
</div>
|
|
23
23
|
</template>
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<u-modal
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
:open="open"
|
|
4
|
+
:title="t('dashboard.configure_widget', 'Configure widget')"
|
|
5
|
+
@update:open="emit('update:open', $event)"
|
|
6
6
|
>
|
|
7
7
|
<template #body>
|
|
8
8
|
<div class="space-y-6">
|
|
9
9
|
<div
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
v-if="hasDataSource"
|
|
11
|
+
class="space-y-3"
|
|
12
12
|
>
|
|
13
13
|
<p class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
14
14
|
{{ t('dashboard.data_source', 'Data source') }}
|
|
15
15
|
</p>
|
|
16
16
|
<div class="flex gap-2">
|
|
17
17
|
<button
|
|
18
|
-
|
|
18
|
+
:class="draftSource === DATA_SOURCE_TYPE.GLOBAL
|
|
19
19
|
? 'bg-primary-500 text-white border-primary-500'
|
|
20
20
|
: 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'"
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
class="flex-1 px-3 py-2 rounded-lg text-sm font-medium border transition-colors"
|
|
22
|
+
@click="draftSource = DATA_SOURCE_TYPE.GLOBAL"
|
|
23
23
|
>
|
|
24
24
|
{{ t('dashboard.global_project', 'Global') }}
|
|
25
25
|
</button>
|
|
26
26
|
<button
|
|
27
|
-
|
|
27
|
+
:class="draftSource === DATA_SOURCE_TYPE.PROJECT
|
|
28
28
|
? 'bg-primary-500 text-white border-primary-500'
|
|
29
29
|
: 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'"
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
class="flex-1 px-3 py-2 rounded-lg text-sm font-medium border transition-colors"
|
|
31
|
+
@click="draftSource = DATA_SOURCE_TYPE.PROJECT"
|
|
32
32
|
>
|
|
33
33
|
{{ t('dashboard.specific_project', 'Specific project') }}
|
|
34
34
|
</button>
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
|
|
37
37
|
<div v-if="draftSource === DATA_SOURCE_TYPE.PROJECT">
|
|
38
38
|
<u-select
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
v-model="draftProjectId"
|
|
40
|
+
:items="projectItems"
|
|
41
|
+
:placeholder="t('dashboard.select_project', 'Select a project')"
|
|
42
42
|
/>
|
|
43
43
|
</div>
|
|
44
44
|
</div>
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
{{ t('dashboard.custom_title', 'Custom title') }}
|
|
49
49
|
</p>
|
|
50
50
|
<u-input
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
v-model="draftTitle"
|
|
52
|
+
:placeholder="t('dashboard.default_title', 'Default title')"
|
|
53
53
|
/>
|
|
54
54
|
</div>
|
|
55
55
|
</div>
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
<template #footer>
|
|
59
59
|
<div class="flex justify-end gap-2">
|
|
60
60
|
<u-button
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
color="neutral"
|
|
62
|
+
variant="ghost"
|
|
63
|
+
@click="emit('update:open', false)"
|
|
64
64
|
>
|
|
65
65
|
{{ t('common.cancel', 'Cancel') }}
|
|
66
66
|
</u-button>
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<u-modal
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
v-model:open="open"
|
|
4
|
+
:title="t('dashboard.add_widget', 'Add a widget')"
|
|
5
|
+
:ui="{ width: 'max-w-2xl' }"
|
|
6
6
|
>
|
|
7
7
|
<template #body>
|
|
8
8
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 p-1">
|
|
9
9
|
<div
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
v-for="[type, config] in availableWidgets"
|
|
11
|
+
:key="type"
|
|
12
|
+
class="border border-gray-200 dark:border-gray-700 rounded-xl p-4 space-y-3 hover:border-primary-300 dark:hover:border-primary-600 transition-colors"
|
|
13
13
|
>
|
|
14
14
|
<div class="flex items-start gap-3">
|
|
15
15
|
<div
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
:class="config.isCustom ? 'bg-primary-50 dark:bg-primary-950' : 'bg-gray-100 dark:bg-gray-800'"
|
|
17
|
+
class="w-9 h-9 rounded-lg flex items-center justify-center shrink-0"
|
|
18
18
|
>
|
|
19
19
|
<u-icon
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
:class="config.isCustom ? 'text-primary-500' : 'text-gray-600 dark:text-gray-400'"
|
|
21
|
+
:name="config.icon"
|
|
22
22
|
/>
|
|
23
23
|
</div>
|
|
24
24
|
<div class="min-w-0 flex-1">
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
{{ config.label }}
|
|
28
28
|
</p>
|
|
29
29
|
<span
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
v-if="config.isCustom"
|
|
31
|
+
class="text-[10px] px-1.5 py-0.5 rounded-full bg-primary-100 dark:bg-primary-900 text-primary-600 dark:text-primary-400 font-medium leading-none"
|
|
32
32
|
>
|
|
33
33
|
{{ t('dashboard.custom', 'Custom') }}
|
|
34
34
|
</span>
|
|
@@ -41,23 +41,23 @@
|
|
|
41
41
|
|
|
42
42
|
<div class="flex items-center gap-1.5 flex-wrap">
|
|
43
43
|
<button
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
v-for="s in config.sizes"
|
|
45
|
+
:key="s"
|
|
46
|
+
:class="getSelectedSize(type) === s
|
|
47
47
|
? 'bg-primary-500 border-primary-500 text-white'
|
|
48
48
|
: 'bg-transparent border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:border-primary-400'"
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
class="px-2 py-0.5 text-xs rounded-md border transition-colors"
|
|
50
|
+
@click="selectedSizes[type] = s"
|
|
51
51
|
>
|
|
52
52
|
{{ s }}
|
|
53
53
|
</button>
|
|
54
54
|
</div>
|
|
55
55
|
|
|
56
56
|
<u-button
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
class="w-full justify-center"
|
|
58
|
+
size="xs"
|
|
59
|
+
variant="soft"
|
|
60
|
+
@click="addWidget(type)"
|
|
61
61
|
>
|
|
62
62
|
{{ t('common.add', 'Add') }}
|
|
63
63
|
</u-button>
|
|
@@ -3,35 +3,35 @@
|
|
|
3
3
|
<template #header>
|
|
4
4
|
<div class="flex items-center gap-2">
|
|
5
5
|
<u-icon
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
class="text-gray-400"
|
|
7
|
+
name="i-heroicons-clock"
|
|
8
8
|
/>
|
|
9
9
|
<span class="text-sm font-semibold text-gray-700 dark:text-gray-300">{{ displayTitle }}</span>
|
|
10
10
|
<span
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
v-if="sourceLabel"
|
|
12
|
+
class="ml-auto text-xs text-gray-400 dark:text-gray-500"
|
|
13
13
|
>{{ sourceLabel }}</span>
|
|
14
14
|
</div>
|
|
15
15
|
</template>
|
|
16
16
|
|
|
17
17
|
<div
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
v-if="pending"
|
|
19
|
+
class="space-y-2"
|
|
20
20
|
>
|
|
21
21
|
<u-skeleton
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
v-for="i in 4"
|
|
23
|
+
:key="i"
|
|
24
|
+
class="h-8 w-full"
|
|
25
25
|
/>
|
|
26
26
|
</div>
|
|
27
27
|
|
|
28
28
|
<div
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
v-else-if="!hasProject"
|
|
30
|
+
class="flex flex-col items-center justify-center h-full py-6 text-center"
|
|
31
31
|
>
|
|
32
32
|
<u-icon
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
class="text-3xl text-gray-300 dark:text-gray-600 mb-2"
|
|
34
|
+
name="i-heroicons-clock"
|
|
35
35
|
/>
|
|
36
36
|
<p class="text-sm text-gray-400">
|
|
37
37
|
{{ t('dashboard.select_project', 'Select a project') }}
|
|
@@ -39,12 +39,12 @@
|
|
|
39
39
|
</div>
|
|
40
40
|
|
|
41
41
|
<div
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
v-else-if="!displayedActivity.length"
|
|
43
|
+
class="flex flex-col items-center justify-center h-full py-6 text-center"
|
|
44
44
|
>
|
|
45
45
|
<u-icon
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
class="text-3xl text-gray-300 dark:text-gray-600 mb-2"
|
|
47
|
+
name="i-heroicons-clock"
|
|
48
48
|
/>
|
|
49
49
|
<p class="text-sm text-gray-400">
|
|
50
50
|
{{ t('dashboard.no_activity', 'No recent activity') }}
|
|
@@ -52,19 +52,19 @@
|
|
|
52
52
|
</div>
|
|
53
53
|
|
|
54
54
|
<div
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
v-else
|
|
56
|
+
:class="size === WIDGET_SIZE.WIDE ? 'grid grid-cols-2 gap-x-4 gap-y-2' : 'space-y-2'"
|
|
57
|
+
class="overflow-y-auto"
|
|
58
58
|
>
|
|
59
59
|
<div
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
v-for="item in displayedActivity"
|
|
61
|
+
:key="item.id"
|
|
62
|
+
class="flex items-start gap-2 py-1"
|
|
63
63
|
>
|
|
64
64
|
<div class="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center shrink-0 mt-0.5">
|
|
65
65
|
<u-icon
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
:name="activityIcon(item.changed_by)"
|
|
67
|
+
class="text-xs text-gray-500 dark:text-gray-400"
|
|
68
68
|
/>
|
|
69
69
|
</div>
|
|
70
70
|
<div class="min-w-0 flex-1">
|
|
@@ -73,10 +73,10 @@
|
|
|
73
73
|
</p>
|
|
74
74
|
<div class="flex items-center gap-1.5 mt-0.5">
|
|
75
75
|
<u-badge
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
:label="item.language_code.toUpperCase()"
|
|
77
|
+
color="neutral"
|
|
78
|
+
size="xs"
|
|
79
|
+
variant="soft"
|
|
80
80
|
/>
|
|
81
81
|
<span class="text-xs text-gray-400">{{ formatRelative(item.changed_at) }}</span>
|
|
82
82
|
</div>
|
|
@@ -1,38 +1,39 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
|
|
3
|
+
class="w-full h-full relative rounded-xl overflow-hidden border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900"
|
|
4
|
+
>
|
|
4
5
|
<!-- Edit-mode overlay -->
|
|
5
6
|
<div
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
v-if="editing"
|
|
8
|
+
class="absolute inset-0 z-10 bg-gray-900/30 flex flex-col items-center justify-center gap-2 pointer-events-none"
|
|
8
9
|
>
|
|
9
10
|
<u-icon
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
class="text-white text-3xl"
|
|
12
|
+
name="i-heroicons-puzzle-piece"
|
|
12
13
|
/>
|
|
13
14
|
<span class="text-white text-xs font-medium">{{ label }}</span>
|
|
14
15
|
</div>
|
|
15
16
|
|
|
16
17
|
<iframe
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
v-if="url"
|
|
19
|
+
:src="url"
|
|
20
|
+
:title="label"
|
|
21
|
+
class="w-full h-full border-0"
|
|
22
|
+
loading="lazy"
|
|
23
|
+
sandbox="allow-scripts"
|
|
23
24
|
/>
|
|
24
25
|
<div
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
v-else
|
|
27
|
+
class="w-full h-full flex flex-col items-center justify-center text-gray-400 gap-2"
|
|
27
28
|
>
|
|
28
29
|
<u-icon
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
class="text-3xl"
|
|
31
|
+
name="i-heroicons-puzzle-piece"
|
|
31
32
|
/>
|
|
32
33
|
<span class="text-xs">{{ label }}</span>
|
|
33
34
|
<span class="text-xs text-gray-300 dark:text-gray-600">{{
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
t('dashboard.widget_no_url', 'No URL configured')
|
|
36
|
+
}}</span>
|
|
36
37
|
</div>
|
|
37
38
|
</div>
|
|
38
39
|
</template>
|
|
@@ -3,35 +3,35 @@
|
|
|
3
3
|
<template #header>
|
|
4
4
|
<div class="flex items-center gap-2">
|
|
5
5
|
<u-icon
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
class="text-gray-400"
|
|
7
|
+
name="i-heroicons-globe-alt"
|
|
8
8
|
/>
|
|
9
9
|
<span class="text-sm font-semibold text-gray-700 dark:text-gray-300">{{ displayTitle }}</span>
|
|
10
10
|
<span
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
v-if="sourceLabel"
|
|
12
|
+
class="ml-auto text-xs text-gray-400 dark:text-gray-500"
|
|
13
13
|
>{{ sourceLabel }}</span>
|
|
14
14
|
</div>
|
|
15
15
|
</template>
|
|
16
16
|
|
|
17
17
|
<div
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
v-if="pending"
|
|
19
|
+
class="space-y-3"
|
|
20
20
|
>
|
|
21
21
|
<u-skeleton
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
v-for="i in 3"
|
|
23
|
+
:key="i"
|
|
24
|
+
class="h-8 w-full"
|
|
25
25
|
/>
|
|
26
26
|
</div>
|
|
27
27
|
|
|
28
28
|
<div
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
v-else-if="!hasProject"
|
|
30
|
+
class="flex flex-col items-center justify-center h-full py-6 text-center"
|
|
31
31
|
>
|
|
32
32
|
<u-icon
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
class="text-3xl text-gray-300 dark:text-gray-600 mb-2"
|
|
34
|
+
name="i-heroicons-globe-alt"
|
|
35
35
|
/>
|
|
36
36
|
<p class="text-sm text-gray-400">
|
|
37
37
|
{{ t('dashboard.select_project', 'Select a project') }}
|
|
@@ -39,12 +39,12 @@
|
|
|
39
39
|
</div>
|
|
40
40
|
|
|
41
41
|
<div
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
v-else-if="!allLanguages.length"
|
|
43
|
+
class="flex flex-col items-center justify-center h-full py-6 text-center"
|
|
44
44
|
>
|
|
45
45
|
<u-icon
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
class="text-3xl text-gray-300 dark:text-gray-600 mb-2"
|
|
47
|
+
name="i-heroicons-globe-alt"
|
|
48
48
|
/>
|
|
49
49
|
<p class="text-sm text-gray-400">
|
|
50
50
|
{{ t('dashboard.no_languages', 'No language configured') }}
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
|
|
54
54
|
<template v-else>
|
|
55
55
|
<div
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
:class="size === WIDGET_SIZE.WIDE ? 'grid grid-cols-2 gap-3 space-y-0' : 'space-y-3'"
|
|
57
|
+
class="overflow-y-auto flex-1"
|
|
58
58
|
>
|
|
59
59
|
<div
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
v-for="lang in displayedLanguages"
|
|
61
|
+
:key="lang.code"
|
|
62
|
+
class="space-y-1"
|
|
63
63
|
>
|
|
64
64
|
<div class="flex items-center justify-between text-xs">
|
|
65
65
|
<span class="font-medium text-gray-700 dark:text-gray-300">
|
|
@@ -67,22 +67,22 @@
|
|
|
67
67
|
<span class="text-gray-400 ml-1 font-mono">{{ lang.code }}</span>
|
|
68
68
|
</span>
|
|
69
69
|
<span
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
:class="lang.coverage >= 90 ? 'text-green-600' : lang.coverage >= 60 ? 'text-yellow-500' : 'text-red-500'"
|
|
71
|
+
class="font-semibold"
|
|
72
72
|
>
|
|
73
73
|
{{ lang.coverage.toFixed(2) }}%
|
|
74
74
|
</span>
|
|
75
75
|
</div>
|
|
76
76
|
<div class="w-full bg-gray-100 dark:bg-gray-800 rounded-full h-1.5">
|
|
77
77
|
<div
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
:class="coverageColor(lang.coverage)"
|
|
79
|
+
:style="{ width: `${lang.coverage}%` }"
|
|
80
|
+
class="h-1.5 rounded-full transition-all"
|
|
81
81
|
/>
|
|
82
82
|
</div>
|
|
83
83
|
<p
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
v-if="size !== WIDGET_SIZE.SM"
|
|
85
|
+
class="text-xs text-gray-400"
|
|
86
86
|
>
|
|
87
87
|
{{ lang.translated }} / {{ lang.total }} · {{ lang.missing }} {{ t('dashboard.missing', 'missing') }}
|
|
88
88
|
</p>
|
|
@@ -90,23 +90,29 @@
|
|
|
90
90
|
</div>
|
|
91
91
|
|
|
92
92
|
<div
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
v-if="totalPages > 1"
|
|
94
|
+
class="flex items-center justify-between pt-2 mt-2 border-t border-gray-100 dark:border-gray-800"
|
|
95
95
|
>
|
|
96
96
|
<button
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
:disabled="page === 1"
|
|
98
|
+
class="p-1 rounded text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
|
|
99
|
+
@click="page--"
|
|
100
100
|
>
|
|
101
|
-
<u-icon
|
|
101
|
+
<u-icon
|
|
102
|
+
name="i-heroicons-chevron-left"
|
|
103
|
+
class="text-sm"
|
|
104
|
+
/>
|
|
102
105
|
</button>
|
|
103
106
|
<span class="text-xs text-gray-400">{{ page }} / {{ totalPages }}</span>
|
|
104
107
|
<button
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
:disabled="page === totalPages"
|
|
109
|
+
class="p-1 rounded text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
|
|
110
|
+
@click="page++"
|
|
108
111
|
>
|
|
109
|
-
<u-icon
|
|
112
|
+
<u-icon
|
|
113
|
+
name="i-heroicons-chevron-right"
|
|
114
|
+
class="text-sm"
|
|
115
|
+
/>
|
|
110
116
|
</button>
|
|
111
117
|
</div>
|
|
112
118
|
</template>
|