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.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/src/components/common/FormatImportModal.vue +15 -3
  3. package/src/components/common/FormatSnippet.vue +8 -8
  4. package/src/components/common/WidgetConfigModal.vue +19 -19
  5. package/src/components/common/WidgetPicker.vue +21 -21
  6. package/src/components/common/widgets/ActivityWidget.vue +29 -29
  7. package/src/components/common/widgets/CustomIframeWidget.vue +18 -17
  8. package/src/components/common/widgets/LanguagesCoverageWidget.vue +45 -39
  9. package/src/components/common/widgets/ProjectsWidget.vue +30 -30
  10. package/src/components/common/widgets/ReviewWidget.vue +39 -39
  11. package/src/components/common/widgets/StatWidget.vue +19 -19
  12. package/src/components/dashboard/WidgetGrid.vue +61 -61
  13. package/src/components/project/WidgetGrid.vue +61 -61
  14. package/src/layouts/auth.vue +7 -6
  15. package/src/layouts/default.vue +157 -154
  16. package/src/pages/admin/customization.vue +124 -124
  17. package/src/pages/admin/security.vue +37 -35
  18. package/src/pages/admin/smtp.vue +80 -78
  19. package/src/pages/login.vue +39 -39
  20. package/src/pages/onboarding.vue +247 -246
  21. package/src/pages/projects/[id]/formats/datetime.vue +98 -94
  22. package/src/pages/projects/[id]/formats/modifiers.vue +80 -76
  23. package/src/pages/projects/[id]/formats/number.vue +113 -109
  24. package/src/pages/projects/[id]/languages.vue +209 -202
  25. package/src/pages/projects/[id]/review.vue +73 -69
  26. package/src/pages/projects/[id]/settings.vue +158 -158
  27. package/src/pages/projects/[id]/translations/[keyId].vue +227 -222
  28. package/src/pages/projects/[id]/translations/index.vue +108 -108
  29. package/src/pages/projects/[id]/users.vue +162 -158
  30. package/src/pages/reset-password.vue +44 -44
  31. package/src/pages/users/[id]/profile.vue +159 -155
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18n-dashboard",
3
- "version": "0.18.0",
3
+ "version": "0.18.1",
4
4
  "description": "A web dashboard to manage vue-i18n translation keys with database persistence",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 :model-value="f.checked" @click.stop @update:model-value="f.checked = Boolean($event)" />
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 :model-value="f.checked" @click.stop @update:model-value="f.checked = Boolean($event)" />
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 :model-value="m.checked" @click.stop @update:model-value="m.checked = Boolean($event)" />
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
- class="text-green-500"
8
- name="i-heroicons-code-bracket"
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
- :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"
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
- :open="open"
4
- :title="t('dashboard.configure_widget', 'Configure widget')"
5
- @update:open="emit('update:open', $event)"
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
- v-if="hasDataSource"
11
- class="space-y-3"
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
- :class="draftSource === DATA_SOURCE_TYPE.GLOBAL
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
- class="flex-1 px-3 py-2 rounded-lg text-sm font-medium border transition-colors"
22
- @click="draftSource = DATA_SOURCE_TYPE.GLOBAL"
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
- :class="draftSource === DATA_SOURCE_TYPE.PROJECT
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
- class="flex-1 px-3 py-2 rounded-lg text-sm font-medium border transition-colors"
31
- @click="draftSource = DATA_SOURCE_TYPE.PROJECT"
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
- v-model="draftProjectId"
40
- :items="projectItems"
41
- :placeholder="t('dashboard.select_project', 'Select a project')"
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
- v-model="draftTitle"
52
- :placeholder="t('dashboard.default_title', 'Default title')"
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
- color="neutral"
62
- variant="ghost"
63
- @click="emit('update:open', false)"
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
- v-model:open="open"
4
- :title="t('dashboard.add_widget', 'Add a widget')"
5
- :ui="{ width: 'max-w-2xl' }"
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
- 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"
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
- :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"
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
- :class="config.isCustom ? 'text-primary-500' : 'text-gray-600 dark:text-gray-400'"
21
- :name="config.icon"
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
- 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"
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
- v-for="s in config.sizes"
45
- :key="s"
46
- :class="getSelectedSize(type) === s
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
- class="px-2 py-0.5 text-xs rounded-md border transition-colors"
50
- @click="selectedSizes[type] = s"
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
- class="w-full justify-center"
58
- size="xs"
59
- variant="soft"
60
- @click="addWidget(type)"
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
- class="text-gray-400"
7
- name="i-heroicons-clock"
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
- v-if="sourceLabel"
12
- class="ml-auto text-xs text-gray-400 dark:text-gray-500"
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
- v-if="pending"
19
- class="space-y-2"
18
+ v-if="pending"
19
+ class="space-y-2"
20
20
  >
21
21
  <u-skeleton
22
- v-for="i in 4"
23
- :key="i"
24
- class="h-8 w-full"
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
- v-else-if="!hasProject"
30
- class="flex flex-col items-center justify-center h-full py-6 text-center"
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
- class="text-3xl text-gray-300 dark:text-gray-600 mb-2"
34
- name="i-heroicons-clock"
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
- v-else-if="!displayedActivity.length"
43
- class="flex flex-col items-center justify-center h-full py-6 text-center"
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
- class="text-3xl text-gray-300 dark:text-gray-600 mb-2"
47
- name="i-heroicons-clock"
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
- 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"
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
- v-for="item in displayedActivity"
61
- :key="item.id"
62
- class="flex items-start gap-2 py-1"
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
- :name="activityIcon(item.changed_by)"
67
- class="text-xs text-gray-500 dark:text-gray-400"
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
- :label="item.language_code.toUpperCase()"
77
- color="neutral"
78
- size="xs"
79
- variant="soft"
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
- class="w-full h-full relative rounded-xl overflow-hidden border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900">
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
- v-if="editing"
7
- class="absolute inset-0 z-10 bg-gray-900/30 flex flex-col items-center justify-center gap-2 pointer-events-none"
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
- class="text-white text-3xl"
11
- name="i-heroicons-puzzle-piece"
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
- v-if="url"
18
- :src="url"
19
- :title="label"
20
- class="w-full h-full border-0"
21
- loading="lazy"
22
- sandbox="allow-scripts"
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
- v-else
26
- class="w-full h-full flex flex-col items-center justify-center text-gray-400 gap-2"
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
- class="text-3xl"
30
- name="i-heroicons-puzzle-piece"
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
- t('dashboard.widget_no_url', 'No URL configured')
35
- }}</span>
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
- class="text-gray-400"
7
- name="i-heroicons-globe-alt"
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
- v-if="sourceLabel"
12
- class="ml-auto text-xs text-gray-400 dark:text-gray-500"
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
- v-if="pending"
19
- class="space-y-3"
18
+ v-if="pending"
19
+ class="space-y-3"
20
20
  >
21
21
  <u-skeleton
22
- v-for="i in 3"
23
- :key="i"
24
- class="h-8 w-full"
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
- v-else-if="!hasProject"
30
- class="flex flex-col items-center justify-center h-full py-6 text-center"
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
- class="text-3xl text-gray-300 dark:text-gray-600 mb-2"
34
- name="i-heroicons-globe-alt"
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
- v-else-if="!allLanguages.length"
43
- class="flex flex-col items-center justify-center h-full py-6 text-center"
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
- class="text-3xl text-gray-300 dark:text-gray-600 mb-2"
47
- name="i-heroicons-globe-alt"
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
- :class="size === WIDGET_SIZE.WIDE ? 'grid grid-cols-2 gap-3 space-y-0' : 'space-y-3'"
57
- class="overflow-y-auto flex-1"
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
- v-for="lang in displayedLanguages"
61
- :key="lang.code"
62
- class="space-y-1"
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
- :class="lang.coverage >= 90 ? 'text-green-600' : lang.coverage >= 60 ? 'text-yellow-500' : 'text-red-500'"
71
- class="font-semibold"
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
- :class="coverageColor(lang.coverage)"
79
- :style="{ width: `${lang.coverage}%` }"
80
- class="h-1.5 rounded-full transition-all"
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
- v-if="size !== WIDGET_SIZE.SM"
85
- class="text-xs text-gray-400"
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
- v-if="totalPages > 1"
94
- class="flex items-center justify-between pt-2 mt-2 border-t border-gray-100 dark:border-gray-800"
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
- :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--"
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 name="i-heroicons-chevron-left" class="text-sm" />
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
- :disabled="page === totalPages"
106
- 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"
107
- @click="page++"
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 name="i-heroicons-chevron-right" class="text-sm" />
112
+ <u-icon
113
+ name="i-heroicons-chevron-right"
114
+ class="text-sm"
115
+ />
110
116
  </button>
111
117
  </div>
112
118
  </template>