@adminforth/i18n 2.5.1 → 3.1.0

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/build.log CHANGED
@@ -20,5 +20,5 @@ custom/package.json
20
20
  custom/pnpm-lock.yaml
21
21
  custom/tsconfig.json
22
22
 
23
- sent 47,235 bytes received 305 bytes 95,080.00 bytes/sec
24
- total size is 46,083 speedup is 0.97
23
+ sent 44,008 bytes received 305 bytes 88,626.00 bytes/sec
24
+ total size is 42,840 speedup is 0.97
@@ -4,47 +4,44 @@
4
4
  class="w-[600px]"
5
5
  :buttons="[
6
6
  {
7
- label: 'Translate',
8
- onclick: async (dialog) => { await runTranslation(); dialog.hide(); } ,
7
+ label: t('Close'),
9
8
  options: {
10
- disabled: noneChecked
11
- }
9
+ variant: 'secondary'
10
+ },
11
+ onclick: (dialog) => dialog.hide()
12
12
  },
13
13
  {
14
- label: 'Close',
14
+ label: t('Translate'),
15
+ onclick: async (dialog) => { await runTranslation(); dialog.hide(); } ,
15
16
  options: {
16
- class: 'bg-white hover:!bg-gray-100 !text-gray-900 hover:!text-gray-800 dark:!bg-gray-800 dark:!text-gray-100 dark:hover:!bg-gray-700 !border-gray-200 dark:!border-gray-600'
17
- },
18
- onclick: (dialog) => dialog.hide()
17
+ disabled: noneChecked
18
+ }
19
19
  },
20
20
  ]"
21
21
  >
22
22
  <template #trigger>
23
- <button class="flex items-center justify-center w-full">
24
- <IconLanguageOutline class="text-gray-500 dark:text-gray-400 w-5 h-5" />
25
- <div class="flex items-end justify-start gap-2 cursor-pointer">
26
- <p class="text-justify max-h-[18px] truncate max-w-[60vw] md:max-w-none">{{props.checkboxes.length === 0 ? t('Translate filtered') : t('Translate selected ({count})', { count: props.checkboxes.length })}}</p>
27
- <div class="flex items-center justify-center text-white bg-gradient-to-r h-[18px] from-purple-500 via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800 font-medium rounded-md text-sm px-1 text-center">
28
- {{t('AI')}}
29
- </div>
30
- </div>
31
- </button>
23
+ <span class="flex items-center gap-2">
24
+ <IconLanguages class="w-4 h-4 shrink-0 text-lightInputIconColor dark:text-darkInputIconColor" aria-hidden="true" />
25
+ <span class="truncate max-w-[60vw] md:max-w-none">{{props.checkboxes.length === 0 ? t('Translate filtered') : t('Translate selected ({count})', { count: props.checkboxes.length })}}</span>
26
+ <span class="inline-flex items-center h-[1.125rem] px-1.5 rounded-full border border-lightPrimary/30 bg-lightPrimary/10 text-lightPrimary text-[11px] font-semibold dark:border-darkPrimary/40 dark:bg-darkPrimary/15 dark:text-darkPrimary">
27
+ {{t('AI')}}
28
+ </span>
29
+ </span>
32
30
  </template>
33
31
 
34
- <div class="af-i18n-translations-selector grid grid-cols-2 gap-1 w-full">
32
+ <div class="af-i18n-translations-selector relative grid grid-cols-2 gap-2 w-full">
35
33
  <div v-if="isLoading" class="top-0 left-0 z-10 absolute bg-black/30 w-full h-full rounded-lg flex items-center justify-center">
36
34
  <Spinner class="w-10 h-10" />
37
35
  </div>
38
- <Button @click="selectAll" :disabled="allChecked">{{ t('Select All') }}</Button>
39
- <Button @click="uncheckAll" :disabled="noneChecked">{{ t('Uncheck All') }}</Button>
40
- <div class="col-span-2 grid grid-cols-3 gap-1 mt-4">
41
- <div class="group hover:bg-gray-100 dark:hover:bg-gray-600 px-2 py-2 flex items-center cursor-pointer" v-for="(index, lang) in checkedLanguages" :key="lang" @click="toggleLanguage(lang)">
42
- <Checkbox v-model="checkedLanguages[lang]" />
43
- <span class="flag-icon mr-2"
44
- :class="`flag-icon-${props.meta.countryCodes[lang]}`"
45
- ></span>
46
- <span class="group-hover:text-gray-900 text-gray-600 dark:text-white dark:group-hover:text-gray-200">{{ ISO6391.getName(lang.slice(0,2)) }} ({{ lang }})</span>
47
- </div>
36
+ <Button variant="secondary" @click="selectAll" :disabled="allChecked">{{ t('Select All') }}</Button>
37
+ <Button variant="secondary" @click="uncheckAll" :disabled="noneChecked">{{ t('Uncheck All') }}</Button>
38
+ <div class="col-span-2 grid grid-cols-3 gap-x-2 gap-y-3 mt-4">
39
+ <Checkbox v-for="(index, lang) in checkedLanguages" :key="lang" v-model="checkedLanguages[lang]">
40
+ <span class="flex items-center gap-2">
41
+ <CountryFlag size="sm" :countryCode="props.meta.countryCodes[lang]" />
42
+ {{ ISO6391.getName(lang.slice(0,2)) }} ({{ lang }})
43
+ </span>
44
+ </Checkbox>
48
45
  </div>
49
46
  </div>
50
47
  </Dialog>
@@ -52,15 +49,14 @@
52
49
  </template>
53
50
 
54
51
  <script setup lang="ts">
55
- import { IconLanguageOutline } from '@iconify-prerendered/vue-flowbite';
52
+ import { IconLanguages } from '@iconify-prerendered/vue-lucide';
56
53
  import { useI18n } from 'vue-i18n';
57
- import { Dialog, Button, Checkbox, Spinner } from '@/afcl';
54
+ import { Dialog, Button, Checkbox, CountryFlag, Spinner } from '@/afcl';
58
55
  import { computed, onMounted, ref, onUnmounted } from 'vue';
59
56
  import { callAdminForthApi } from '@/utils';
60
57
  import { useAdminforth } from '@/adminforth';
61
58
  import { getName, overwrite } from 'country-list';
62
59
  import ISO6391 from 'iso-639-1';
63
- import 'flag-icon-css/css/flag-icons.min.css';
64
60
  import websocket from '@/websocket';
65
61
  import { useFiltersStore } from '@/stores/filters';
66
62
  import { useBackgroundJobApi } from '@/custom/plugins/BackgroundJobsPlugin/useBackgroundJobApi.ts';
@@ -127,10 +123,6 @@
127
123
  }
128
124
  }
129
125
 
130
- function toggleLanguage(lang: string) {
131
- checkedLanguages.value[lang] = !checkedLanguages.value[lang];
132
- }
133
-
134
126
  async function runTranslation() {
135
127
  isLoading.value = true;
136
128
 
@@ -1,52 +1,25 @@
1
1
  <template>
2
- <div class="min-w-40">
3
- <div class="cursor-pointer flex items-center justify-between gap-1 block px-4 py-2 text-sm
4
- bg-lightUserMenuItemBackground hover:bg-lightUserMenuItemBackgroundHover text-lightUserMenuItemText
5
- hover:text-lightUserMenuItemText dark:bg-darkUserMenuItemBackground dark:hover:bg-darkUserMenuItemBackgroundHover
6
- dark:text-darkUserMenuItemText dark:hover:darkUserMenuItemTextHover w-full select-none"
7
- :class="{ 'bg-black bg-opacity-10 ': showDropdown }"
8
- @click="showDropdown = !showDropdown"
2
+ <DropdownMenuAccordion class="af-language-in-user-menu" :label="selectedOption.label" :triggerClass="USER_MENU_ITEM_CLASSES">
3
+ <template #icon>
4
+ <CountryFlag size="sm" :countryCode="selectedOption.countryCode" />
5
+ </template>
6
+ <DropdownMenuItem
7
+ v-for="option in options.filter((opt) => opt.value !== selectedOption.value)"
8
+ :key="option.value"
9
+ :class="USER_MENU_ITEM_CLASSES"
10
+ @click="doChangeLang(option.value)"
9
11
  >
10
- <div>
11
- <span class="mr-1">
12
- <span class="flag-icon"
13
- :class="`flag-icon-${selectedOption.countryCode}`"
14
- ></span>
15
- </span>
16
- <span>{{ selectedOption.label }}</span>
17
- </div>
18
- <IconCaretDownSolid class="h-5 w-5 text-lightPrimary dark:text-gray-400 opacity-50 transition duration-150 ease-in"
19
- :class="{ 'transform rotate-180': showDropdown }"
20
- />
21
- </div>
22
-
23
- <div v-if="showDropdown" >
24
-
25
- <div class="cursor-pointer flex items-center gap-1 block px-4 py-1 text-sm
26
- bg-lightUserMenuItemBackground hover:bg-lightUserMenuItemBackgroundHover text-lightUserMenuItemText
27
- hover:text-lightUserMenuItemText dark:bg-darkUserMenuItemBackground dark:hover:bg-darkUserMenuItemBackgroundHover
28
- dark:text-darkUserMenuItemText dark:hover:darkUserMenuItemTextHover
29
- w-full text-select-none pl-5 select-none"
30
- v-for="option in options.filter((opt) => opt.value !== selectedOption.value)"
31
- @click="doChangeLang(option.value)"
32
- >
33
- <span class="mr-1">
34
- <span class="flag-icon"
35
- :class="`flag-icon-${option.countryCode}`"
36
- ></span>
37
- </span>
38
- <span>{{ option.label }}</span>
39
-
40
- </div>
41
- </div>
42
-
43
-
44
- </div>
12
+ <template #icon>
13
+ <CountryFlag size="sm" :countryCode="option.countryCode" />
14
+ </template>
15
+ {{ option.label }}
16
+ </DropdownMenuItem>
17
+ </DropdownMenuAccordion>
45
18
  </template>
46
19
 
47
20
  <script setup>
48
- import 'flag-icon-css/css/flag-icons.min.css';
49
- import { IconCaretDownSolid } from '@iconify-prerendered/vue-flowbite';
21
+ import { USER_MENU_ITEM_CLASSES } from '@/utils/userMenu';
22
+ import { CountryFlag, DropdownMenuAccordion, DropdownMenuItem } from '@/afcl';
50
23
 
51
24
  import { setLang, getLocalLang, setLocalLang, getLanguageOptions } from './langCommon';
52
25
 
@@ -55,7 +28,6 @@ import { useI18n } from 'vue-i18n';
55
28
 
56
29
  const { setLocaleMessage, locale } = useI18n();
57
30
 
58
- const showDropdown = ref(false);
59
31
  const props = defineProps(['meta', 'resource']);
60
32
 
61
33
  const selectedLanguage = ref('');
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <p class="text-gray-500 dark:text-gray-400 font-sm text-left mt-3 flex items-center justify-center">
2
+ <div class="af-language-under-login mt-3">
3
3
  <Select
4
4
  class="w-full"
5
5
  v-model="selectedLanguage"
@@ -7,30 +7,24 @@
7
7
  :placeholder="$t('Select language')"
8
8
  >
9
9
  <template #item="{ option }">
10
- <span class="mr-1">
11
- <span class="flag-icon"
12
- :class="`flag-icon-${option.countryCode}`"
13
- ></span>
14
-
10
+ <span class="flex items-center gap-2">
11
+ <CountryFlag size="sm" :countryCode="option.countryCode" />
12
+ {{ option.label }}
15
13
  </span>
16
- <span>{{ option.label }}</span>
17
14
  </template>
18
15
 
19
- <template #selected-item="{option}">
20
- <span class="mr-1">
21
- <span class="flag-icon"
22
- :class="`flag-icon-${option.countryCode}`"
23
- ></span>
16
+ <template #selected-item="{ option }">
17
+ <span class="flex items-center gap-2">
18
+ <CountryFlag size="sm" :countryCode="option.countryCode" />
19
+ {{ option.label }}
24
20
  </span>
25
- <span>{{ option.label }}</span>
26
21
  </template>
27
22
  </Select>
28
- </p>
23
+ </div>
29
24
  </template>
30
25
 
31
26
  <script setup>
32
- import Select from '@/afcl/Select.vue';
33
- import 'flag-icon-css/css/flag-icons.min.css';
27
+ import { CountryFlag, Select } from '@/afcl';
34
28
  import { setLang, getLocalLang, getLanguageOptions } from './langCommon';
35
29
  import { useCoreStore } from '@/stores/core';
36
30
 
@@ -15,24 +15,27 @@
15
15
  <template #tooltip>
16
16
  {{ record[meta?.reviewedCheckboxesFieldName]?.[props.column.name] ? t('Translation is reviewed') : t('Translation is not reviewed') }}
17
17
  </template>
18
- <IconCheckOutline
18
+ <IconCircleCheck
19
19
  v-if="record[meta?.reviewedCheckboxesFieldName]?.[props.column.name]"
20
- class="w-5 h-5 text-green-500"
20
+ class="w-4 h-4 text-lightSuccess dark:text-darkSuccess"
21
21
  />
22
- <IconQuestionCircleSolid
22
+ <IconCircleHelp
23
23
  v-else
24
- class="w-5 h-5 text-yellow-500"
24
+ class="w-4 h-4 text-lightWarning dark:text-darkWarning"
25
25
  />
26
26
  </Tooltip>
27
27
  </span>
28
28
 
29
- <button
29
+ <Button
30
30
  v-if="!column.editReadonly"
31
31
  @click="startEdit"
32
- class="ml-2 opacity-0 group-hover:opacity-100 transition-opacity"
32
+ variant="ghost"
33
+ size="sm"
34
+ iconOnly
35
+ class="ml-1 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 transition"
33
36
  >
34
- <IconPenSolid class="w-5 h-5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"/>
35
- </button>
37
+ <IconPencil />
38
+ </Button>
36
39
  </div>
37
40
 
38
41
  <!-- Edit mode -->
@@ -53,31 +56,31 @@
53
56
  :setCurrentValue="setCurrentValue"
54
57
  />
55
58
  <div class="flex gap-1">
56
- <button
59
+ <Button
57
60
  @click="saveEdit"
58
61
  :disabled="saving || (originalReviewed === reviewed && originalValue === currentValues[props.column.name])"
59
- class="text-green-600 hover:text-green-700 dark:text-green-500 dark:hover:text-green-400 disabled:opacity-50"
60
-
62
+ :loader="saving"
63
+ iconOnly
61
64
  >
62
- <IconCheckOutline class="w-5 h-5" />
63
- </button>
64
- <button
65
+ <IconCheck />
66
+ </Button>
67
+ <Button
65
68
  @click="cancelEdit"
66
69
  :disabled="saving"
67
- class="text-red-600 hover:text-red-700 dark:text-red-500 dark:hover:text-red-400"
70
+ variant="secondary"
71
+ iconOnly
68
72
  >
69
- <IconXOutline class="w-5 h-5" />
70
- </button>
73
+ <IconX />
74
+ </Button>
71
75
  </div>
72
76
  </div>
73
77
  <div v-if="meta?.reviewedCheckboxesFieldName">
74
- <label class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-0 cursor-pointer select-none">
75
- <Checkbox
76
- v-model="reviewed"
77
- :disabled="saving"
78
- />
78
+ <Checkbox
79
+ v-model="reviewed"
80
+ :disabled="saving"
81
+ >
79
82
  {{ t('Translation is reviewed') }}
80
- </label>
83
+ </Checkbox>
81
84
 
82
85
  </div>
83
86
  </div>
@@ -87,13 +90,12 @@
87
90
 
88
91
  <script setup lang="ts">
89
92
  import { ref, Ref, computed, nextTick, onMounted } from 'vue';
90
- import { IconPenSolid, IconCheckOutline, IconXOutline, IconQuestionCircleSolid } from '@iconify-prerendered/vue-flowbite';
93
+ import { IconPencil, IconCheck, IconX, IconCircleCheck, IconCircleHelp } from '@iconify-prerendered/vue-lucide';
91
94
  import { callAdminForthApi } from '@/utils';
92
95
  import { showErrorTost, showSuccesTost } from '@/composables/useFrontendApi';
93
96
  import ColumnValueInputWrapper from '@/components/ColumnValueInputWrapper.vue';
94
97
  import { useI18n } from 'vue-i18n';
95
- import Tooltip from '@/afcl/Tooltip.vue';
96
- import Checkbox from '@/afcl/Checkbox.vue';
98
+ import { Button, Checkbox, Tooltip } from '@/afcl';
97
99
  import { eventBus } from './eventBus'
98
100
 
99
101
  const listCell = ref(null);
@@ -1,24 +1,22 @@
1
1
  <template>
2
- <Input
2
+ <Input
3
3
  ref="input"
4
4
  v-if="shortValue"
5
- class="w-full"
5
+ type="text"
6
+ fullWidth
6
7
  :modelValue="props.record[props.column.name]"
7
- :disabled="props.column.editReadonly"
8
+ :readonly="props.column.editReadonly"
8
9
  @update:modelValue="($event) => emit('update:value', $event)" />
9
- <textarea
10
+ <Textarea
10
11
  v-else
11
12
  ref="textarea"
12
- :disabled="props.column.editReadonly"
13
- class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg block w-full p-2.5
14
- dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary"
15
- :value="props.record[props.column.name]"
16
- @input="($event) => { autoResizeTextArea(); emit('update:value', $event.target.value) }" />
17
-
13
+ :modelValue="props.record[props.column.name]"
14
+ :readonly="props.column.editReadonly"
15
+ @update:modelValue="($event) => { autoResizeTextArea(); emit('update:value', $event) }" />
18
16
  </template>
19
17
 
20
18
  <script setup lang="ts">
21
- import { Input } from "@/afcl";
19
+ import { Input, Textarea } from "@/afcl";
22
20
  import type {
23
21
  AdminForthResourceColumnCommon,
24
22
  AdminForthResourceCommon,
@@ -38,8 +36,8 @@ const props = defineProps<{
38
36
 
39
37
  const emit = defineEmits(["update:value"]);
40
38
 
41
- const input: Ref<HTMLInputElement | HTMLTextAreaElement | null> = ref(null);
42
- const textarea: Ref<HTMLInputElement | HTMLTextAreaElement | null> = ref(null);
39
+ const input = ref<InstanceType<typeof Input> | null>(null);
40
+ const textarea = ref<InstanceType<typeof Textarea> | null>(null);
43
41
 
44
42
  // Auto resize function
45
43
  const autoResizeTextArea = () => {
@@ -47,10 +45,12 @@ const autoResizeTextArea = () => {
47
45
  nextTick(() => {
48
46
  if (!textarea.value) return
49
47
 
48
+ // the textarea is the root element of AFCL Textarea
49
+ const el = textarea.value.$el as HTMLTextAreaElement
50
50
  // Reset the height to shrink if content is removed
51
- textarea.value.style.height = 'auto'
51
+ el.style.height = 'auto'
52
52
  // Set it to the scrollHeight to make it grow
53
- textarea.value.style.height = textarea.value.scrollHeight + 3 + 'px'
53
+ el.style.height = el.scrollHeight + 3 + 'px'
54
54
  })
55
55
  }
56
56
 
@@ -2,18 +2,18 @@
2
2
  <template>
3
3
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-4 my-3 mt-5">
4
4
  <div class="flex items-center space-x-1">
5
- <span class="text-gray-600 dark:text-gray-300">{{ t('Total tokens will be used for translation:') }}</span>
6
- <span class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ new Number(props.job.state?.totalTranslationTokenCost).toLocaleString() || 0 }}</span>
5
+ <span class="text-lightInputIconColor dark:text-darkInputIconColor">{{ t('Total tokens will be used for translation:') }}</span>
6
+ <span class="text-lg font-semibold tabular-nums text-lightInputText dark:text-darkInputText">{{ new Number(props.job.state?.totalTranslationTokenCost).toLocaleString() || 0 }}</span>
7
7
  </div>
8
8
  <div class="flex items-center space-x-1">
9
- <span class="text-gray-600 dark:text-gray-300">{{ t('Total translation token used:') }}</span>
10
- <span class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ new Number(props.job.state?.totalUsedTokens).toLocaleString() || 0 }}</span>
9
+ <span class="text-lightInputIconColor dark:text-darkInputIconColor">{{ t('Total translation token used:') }}</span>
10
+ <span class="text-lg font-semibold tabular-nums text-lightInputText dark:text-darkInputText">{{ new Number(props.job.state?.totalUsedTokens).toLocaleString() || 0 }}</span>
11
11
  </div>
12
12
  </div>
13
13
  <div class="grid grid-cols-3 gap-2">
14
14
  <div
15
15
  v-if="!isFetchingTasks"
16
- class="bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border-gray-200 dark:border-gray-700 transition-all px-2 py-2 rounded-md border max-w-64 w-full flex items-center justify-between gap-2"
16
+ class="bg-lightSecondaryDarken border-lightTableBorder dark:bg-darkSecondaryLighten dark:border-darkTableBorder px-2 py-2 rounded-default border max-w-64 w-full flex items-center justify-between gap-2"
17
17
  v-for="(task, index) in translationTasks"
18
18
  :key="index"
19
19
  >
@@ -21,9 +21,7 @@
21
21
  {{ task.state?.taskName }} to
22
22
  </span>
23
23
  <div class="flex items-center gap-2 shrink-0">
24
- <span class="flag-icon"
25
- :class="`flag-icon-${task.state?.countryCode}`"
26
- ></span>
24
+ <CountryFlag size="sm" :countryCode="task.state?.countryCode" />
27
25
  <component
28
26
  :is="getCustomComponent({file: '@@/plugins/BackgroundJobsPlugin/StateToIcon.vue'})"
29
27
  :status="task.status"
@@ -38,20 +36,22 @@
38
36
  <div v-if="page > 0 || hasNextPage" class="flex items-center justify-center gap-3 mt-4">
39
37
  <Button
40
38
  variant="secondary"
39
+ iconOnly
41
40
  :disabled="page === 0 || isFetchingTasks"
42
41
  @click="loadPage(page - 1)"
43
42
  >
44
- <IconAngleLeftOutline class="w-4 h-4" />
43
+ <IconChevronLeft />
45
44
  </Button>
46
- <span class="text-sm text-gray-600 dark:text-gray-300">
45
+ <span class="text-sm tabular-nums text-lightInputIconColor dark:text-darkInputIconColor">
47
46
  {{ t('Page {page}', { page: page + 1 }) }}
48
47
  </span>
49
48
  <Button
50
49
  variant="secondary"
50
+ iconOnly
51
51
  :disabled="!hasNextPage || isFetchingTasks"
52
52
  @click="loadPage(page + 1)"
53
53
  >
54
- <IconAngleRightOutline class="w-4 h-4" />
54
+ <IconChevronRight />
55
55
  </Button>
56
56
  </div>
57
57
  </template>
@@ -62,8 +62,8 @@ import { useI18n } from 'vue-i18n'
62
62
  import { computed, onMounted, onUnmounted, ref } from 'vue';
63
63
  import websocket from '@/websocket';
64
64
  import { getCustomComponent } from '@/utils';
65
- import { Button, Spinner } from '@/afcl';
66
- import { IconAngleLeftOutline, IconAngleRightOutline } from '@iconify-prerendered/vue-flowbite';
65
+ import { Button, CountryFlag, Spinner } from '@/afcl';
66
+ import { IconChevronLeft, IconChevronRight } from '@iconify-prerendered/vue-lucide';
67
67
 
68
68
  const { t } = useI18n();
69
69
 
@@ -12,9 +12,7 @@
12
12
  "country-list": "^2.4.1",
13
13
  "iso-639-1": "^3.1.5"
14
14
  },
15
- "devDependencies": {
16
- "flag-icon-css": "^4.1.7"
17
- }
15
+ "devDependencies": {}
18
16
  },
19
17
  "node_modules/country-list": {
20
18
  "version": "2.4.1",
@@ -22,14 +20,6 @@
22
20
  "integrity": "sha512-KhVV/UfUV3dSNpsWIqHTQxLpYDKPKz1UwkRjadt+YbX2PRhyCEihEoS5XgB7J7AMXpkicvl+tRHvkNI5wbji/g==",
23
21
  "license": "MIT"
24
22
  },
25
- "node_modules/flag-icon-css": {
26
- "version": "4.1.7",
27
- "resolved": "https://registry.npmjs.org/flag-icon-css/-/flag-icon-css-4.1.7.tgz",
28
- "integrity": "sha512-AFjSU+fv98XbU0vnTQ32vcLj89UEr1MhwDFcooQv14qWJCjg9fGZzfh9BVyDhAhIOZW/pGmJmq38RqpgPaeybQ==",
29
- "deprecated": "The project has been renamed to flag-icons",
30
- "dev": true,
31
- "license": "MIT"
32
- },
33
23
  "node_modules/iso-639-1": {
34
24
  "version": "3.1.5",
35
25
  "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-3.1.5.tgz",
@@ -10,7 +10,6 @@
10
10
  "license": "ISC",
11
11
  "description": "",
12
12
  "devDependencies": {
13
- "flag-icon-css": "^4.1.7"
14
13
  },
15
14
  "dependencies": {
16
15
  "country-list": "^2.4.1",
@@ -14,20 +14,12 @@ importers:
14
14
  iso-639-1:
15
15
  specifier: ^3.1.5
16
16
  version: 3.1.5
17
- devDependencies:
18
- flag-icon-css:
19
- specifier: ^4.1.7
20
- version: 4.1.7
21
17
 
22
18
  packages:
23
19
 
24
20
  country-list@2.4.1:
25
21
  resolution: {integrity: sha512-KhVV/UfUV3dSNpsWIqHTQxLpYDKPKz1UwkRjadt+YbX2PRhyCEihEoS5XgB7J7AMXpkicvl+tRHvkNI5wbji/g==}
26
22
 
27
- flag-icon-css@4.1.7:
28
- resolution: {integrity: sha512-AFjSU+fv98XbU0vnTQ32vcLj89UEr1MhwDFcooQv14qWJCjg9fGZzfh9BVyDhAhIOZW/pGmJmq38RqpgPaeybQ==}
29
- deprecated: The project has been renamed to flag-icons
30
-
31
23
  iso-639-1@3.1.5:
32
24
  resolution: {integrity: sha512-gXkz5+KN7HrG0Q5UGqSMO2qB9AsbEeyLP54kF1YrMsIxmu+g4BdB7rflReZTSTZGpfj8wywu6pfPBCylPIzGQA==}
33
25
  engines: {node: '>=6.0'}
@@ -36,6 +28,4 @@ snapshots:
36
28
 
37
29
  country-list@2.4.1: {}
38
30
 
39
- flag-icon-css@4.1.7: {}
40
-
41
31
  iso-639-1@3.1.5: {}
@@ -4,47 +4,44 @@
4
4
  class="w-[600px]"
5
5
  :buttons="[
6
6
  {
7
- label: 'Translate',
8
- onclick: async (dialog) => { await runTranslation(); dialog.hide(); } ,
7
+ label: t('Close'),
9
8
  options: {
10
- disabled: noneChecked
11
- }
9
+ variant: 'secondary'
10
+ },
11
+ onclick: (dialog) => dialog.hide()
12
12
  },
13
13
  {
14
- label: 'Close',
14
+ label: t('Translate'),
15
+ onclick: async (dialog) => { await runTranslation(); dialog.hide(); } ,
15
16
  options: {
16
- class: 'bg-white hover:!bg-gray-100 !text-gray-900 hover:!text-gray-800 dark:!bg-gray-800 dark:!text-gray-100 dark:hover:!bg-gray-700 !border-gray-200 dark:!border-gray-600'
17
- },
18
- onclick: (dialog) => dialog.hide()
17
+ disabled: noneChecked
18
+ }
19
19
  },
20
20
  ]"
21
21
  >
22
22
  <template #trigger>
23
- <button class="flex items-center justify-center w-full">
24
- <IconLanguageOutline class="text-gray-500 dark:text-gray-400 w-5 h-5" />
25
- <div class="flex items-end justify-start gap-2 cursor-pointer">
26
- <p class="text-justify max-h-[18px] truncate max-w-[60vw] md:max-w-none">{{props.checkboxes.length === 0 ? t('Translate filtered') : t('Translate selected ({count})', { count: props.checkboxes.length })}}</p>
27
- <div class="flex items-center justify-center text-white bg-gradient-to-r h-[18px] from-purple-500 via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800 font-medium rounded-md text-sm px-1 text-center">
28
- {{t('AI')}}
29
- </div>
30
- </div>
31
- </button>
23
+ <span class="flex items-center gap-2">
24
+ <IconLanguages class="w-4 h-4 shrink-0 text-lightInputIconColor dark:text-darkInputIconColor" aria-hidden="true" />
25
+ <span class="truncate max-w-[60vw] md:max-w-none">{{props.checkboxes.length === 0 ? t('Translate filtered') : t('Translate selected ({count})', { count: props.checkboxes.length })}}</span>
26
+ <span class="inline-flex items-center h-[1.125rem] px-1.5 rounded-full border border-lightPrimary/30 bg-lightPrimary/10 text-lightPrimary text-[11px] font-semibold dark:border-darkPrimary/40 dark:bg-darkPrimary/15 dark:text-darkPrimary">
27
+ {{t('AI')}}
28
+ </span>
29
+ </span>
32
30
  </template>
33
31
 
34
- <div class="af-i18n-translations-selector grid grid-cols-2 gap-1 w-full">
32
+ <div class="af-i18n-translations-selector relative grid grid-cols-2 gap-2 w-full">
35
33
  <div v-if="isLoading" class="top-0 left-0 z-10 absolute bg-black/30 w-full h-full rounded-lg flex items-center justify-center">
36
34
  <Spinner class="w-10 h-10" />
37
35
  </div>
38
- <Button @click="selectAll" :disabled="allChecked">{{ t('Select All') }}</Button>
39
- <Button @click="uncheckAll" :disabled="noneChecked">{{ t('Uncheck All') }}</Button>
40
- <div class="col-span-2 grid grid-cols-3 gap-1 mt-4">
41
- <div class="group hover:bg-gray-100 dark:hover:bg-gray-600 px-2 py-2 flex items-center cursor-pointer" v-for="(index, lang) in checkedLanguages" :key="lang" @click="toggleLanguage(lang)">
42
- <Checkbox v-model="checkedLanguages[lang]" />
43
- <span class="flag-icon mr-2"
44
- :class="`flag-icon-${props.meta.countryCodes[lang]}`"
45
- ></span>
46
- <span class="group-hover:text-gray-900 text-gray-600 dark:text-white dark:group-hover:text-gray-200">{{ ISO6391.getName(lang.slice(0,2)) }} ({{ lang }})</span>
47
- </div>
36
+ <Button variant="secondary" @click="selectAll" :disabled="allChecked">{{ t('Select All') }}</Button>
37
+ <Button variant="secondary" @click="uncheckAll" :disabled="noneChecked">{{ t('Uncheck All') }}</Button>
38
+ <div class="col-span-2 grid grid-cols-3 gap-x-2 gap-y-3 mt-4">
39
+ <Checkbox v-for="(index, lang) in checkedLanguages" :key="lang" v-model="checkedLanguages[lang]">
40
+ <span class="flex items-center gap-2">
41
+ <CountryFlag size="sm" :countryCode="props.meta.countryCodes[lang]" />
42
+ {{ ISO6391.getName(lang.slice(0,2)) }} ({{ lang }})
43
+ </span>
44
+ </Checkbox>
48
45
  </div>
49
46
  </div>
50
47
  </Dialog>
@@ -52,15 +49,14 @@
52
49
  </template>
53
50
 
54
51
  <script setup lang="ts">
55
- import { IconLanguageOutline } from '@iconify-prerendered/vue-flowbite';
52
+ import { IconLanguages } from '@iconify-prerendered/vue-lucide';
56
53
  import { useI18n } from 'vue-i18n';
57
- import { Dialog, Button, Checkbox, Spinner } from '@/afcl';
54
+ import { Dialog, Button, Checkbox, CountryFlag, Spinner } from '@/afcl';
58
55
  import { computed, onMounted, ref, onUnmounted } from 'vue';
59
56
  import { callAdminForthApi } from '@/utils';
60
57
  import { useAdminforth } from '@/adminforth';
61
58
  import { getName, overwrite } from 'country-list';
62
59
  import ISO6391 from 'iso-639-1';
63
- import 'flag-icon-css/css/flag-icons.min.css';
64
60
  import websocket from '@/websocket';
65
61
  import { useFiltersStore } from '@/stores/filters';
66
62
  import { useBackgroundJobApi } from '@/custom/plugins/BackgroundJobsPlugin/useBackgroundJobApi.ts';
@@ -127,10 +123,6 @@
127
123
  }
128
124
  }
129
125
 
130
- function toggleLanguage(lang: string) {
131
- checkedLanguages.value[lang] = !checkedLanguages.value[lang];
132
- }
133
-
134
126
  async function runTranslation() {
135
127
  isLoading.value = true;
136
128
 
@@ -1,52 +1,25 @@
1
1
  <template>
2
- <div class="min-w-40">
3
- <div class="cursor-pointer flex items-center justify-between gap-1 block px-4 py-2 text-sm
4
- bg-lightUserMenuItemBackground hover:bg-lightUserMenuItemBackgroundHover text-lightUserMenuItemText
5
- hover:text-lightUserMenuItemText dark:bg-darkUserMenuItemBackground dark:hover:bg-darkUserMenuItemBackgroundHover
6
- dark:text-darkUserMenuItemText dark:hover:darkUserMenuItemTextHover w-full select-none"
7
- :class="{ 'bg-black bg-opacity-10 ': showDropdown }"
8
- @click="showDropdown = !showDropdown"
2
+ <DropdownMenuAccordion class="af-language-in-user-menu" :label="selectedOption.label" :triggerClass="USER_MENU_ITEM_CLASSES">
3
+ <template #icon>
4
+ <CountryFlag size="sm" :countryCode="selectedOption.countryCode" />
5
+ </template>
6
+ <DropdownMenuItem
7
+ v-for="option in options.filter((opt) => opt.value !== selectedOption.value)"
8
+ :key="option.value"
9
+ :class="USER_MENU_ITEM_CLASSES"
10
+ @click="doChangeLang(option.value)"
9
11
  >
10
- <div>
11
- <span class="mr-1">
12
- <span class="flag-icon"
13
- :class="`flag-icon-${selectedOption.countryCode}`"
14
- ></span>
15
- </span>
16
- <span>{{ selectedOption.label }}</span>
17
- </div>
18
- <IconCaretDownSolid class="h-5 w-5 text-lightPrimary dark:text-gray-400 opacity-50 transition duration-150 ease-in"
19
- :class="{ 'transform rotate-180': showDropdown }"
20
- />
21
- </div>
22
-
23
- <div v-if="showDropdown" >
24
-
25
- <div class="cursor-pointer flex items-center gap-1 block px-4 py-1 text-sm
26
- bg-lightUserMenuItemBackground hover:bg-lightUserMenuItemBackgroundHover text-lightUserMenuItemText
27
- hover:text-lightUserMenuItemText dark:bg-darkUserMenuItemBackground dark:hover:bg-darkUserMenuItemBackgroundHover
28
- dark:text-darkUserMenuItemText dark:hover:darkUserMenuItemTextHover
29
- w-full text-select-none pl-5 select-none"
30
- v-for="option in options.filter((opt) => opt.value !== selectedOption.value)"
31
- @click="doChangeLang(option.value)"
32
- >
33
- <span class="mr-1">
34
- <span class="flag-icon"
35
- :class="`flag-icon-${option.countryCode}`"
36
- ></span>
37
- </span>
38
- <span>{{ option.label }}</span>
39
-
40
- </div>
41
- </div>
42
-
43
-
44
- </div>
12
+ <template #icon>
13
+ <CountryFlag size="sm" :countryCode="option.countryCode" />
14
+ </template>
15
+ {{ option.label }}
16
+ </DropdownMenuItem>
17
+ </DropdownMenuAccordion>
45
18
  </template>
46
19
 
47
20
  <script setup>
48
- import 'flag-icon-css/css/flag-icons.min.css';
49
- import { IconCaretDownSolid } from '@iconify-prerendered/vue-flowbite';
21
+ import { USER_MENU_ITEM_CLASSES } from '@/utils/userMenu';
22
+ import { CountryFlag, DropdownMenuAccordion, DropdownMenuItem } from '@/afcl';
50
23
 
51
24
  import { setLang, getLocalLang, setLocalLang, getLanguageOptions } from './langCommon';
52
25
 
@@ -55,7 +28,6 @@ import { useI18n } from 'vue-i18n';
55
28
 
56
29
  const { setLocaleMessage, locale } = useI18n();
57
30
 
58
- const showDropdown = ref(false);
59
31
  const props = defineProps(['meta', 'resource']);
60
32
 
61
33
  const selectedLanguage = ref('');
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <p class="text-gray-500 dark:text-gray-400 font-sm text-left mt-3 flex items-center justify-center">
2
+ <div class="af-language-under-login mt-3">
3
3
  <Select
4
4
  class="w-full"
5
5
  v-model="selectedLanguage"
@@ -7,30 +7,24 @@
7
7
  :placeholder="$t('Select language')"
8
8
  >
9
9
  <template #item="{ option }">
10
- <span class="mr-1">
11
- <span class="flag-icon"
12
- :class="`flag-icon-${option.countryCode}`"
13
- ></span>
14
-
10
+ <span class="flex items-center gap-2">
11
+ <CountryFlag size="sm" :countryCode="option.countryCode" />
12
+ {{ option.label }}
15
13
  </span>
16
- <span>{{ option.label }}</span>
17
14
  </template>
18
15
 
19
- <template #selected-item="{option}">
20
- <span class="mr-1">
21
- <span class="flag-icon"
22
- :class="`flag-icon-${option.countryCode}`"
23
- ></span>
16
+ <template #selected-item="{ option }">
17
+ <span class="flex items-center gap-2">
18
+ <CountryFlag size="sm" :countryCode="option.countryCode" />
19
+ {{ option.label }}
24
20
  </span>
25
- <span>{{ option.label }}</span>
26
21
  </template>
27
22
  </Select>
28
- </p>
23
+ </div>
29
24
  </template>
30
25
 
31
26
  <script setup>
32
- import Select from '@/afcl/Select.vue';
33
- import 'flag-icon-css/css/flag-icons.min.css';
27
+ import { CountryFlag, Select } from '@/afcl';
34
28
  import { setLang, getLocalLang, getLanguageOptions } from './langCommon';
35
29
  import { useCoreStore } from '@/stores/core';
36
30
 
@@ -15,24 +15,27 @@
15
15
  <template #tooltip>
16
16
  {{ record[meta?.reviewedCheckboxesFieldName]?.[props.column.name] ? t('Translation is reviewed') : t('Translation is not reviewed') }}
17
17
  </template>
18
- <IconCheckOutline
18
+ <IconCircleCheck
19
19
  v-if="record[meta?.reviewedCheckboxesFieldName]?.[props.column.name]"
20
- class="w-5 h-5 text-green-500"
20
+ class="w-4 h-4 text-lightSuccess dark:text-darkSuccess"
21
21
  />
22
- <IconQuestionCircleSolid
22
+ <IconCircleHelp
23
23
  v-else
24
- class="w-5 h-5 text-yellow-500"
24
+ class="w-4 h-4 text-lightWarning dark:text-darkWarning"
25
25
  />
26
26
  </Tooltip>
27
27
  </span>
28
28
 
29
- <button
29
+ <Button
30
30
  v-if="!column.editReadonly"
31
31
  @click="startEdit"
32
- class="ml-2 opacity-0 group-hover:opacity-100 transition-opacity"
32
+ variant="ghost"
33
+ size="sm"
34
+ iconOnly
35
+ class="ml-1 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 transition"
33
36
  >
34
- <IconPenSolid class="w-5 h-5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"/>
35
- </button>
37
+ <IconPencil />
38
+ </Button>
36
39
  </div>
37
40
 
38
41
  <!-- Edit mode -->
@@ -53,31 +56,31 @@
53
56
  :setCurrentValue="setCurrentValue"
54
57
  />
55
58
  <div class="flex gap-1">
56
- <button
59
+ <Button
57
60
  @click="saveEdit"
58
61
  :disabled="saving || (originalReviewed === reviewed && originalValue === currentValues[props.column.name])"
59
- class="text-green-600 hover:text-green-700 dark:text-green-500 dark:hover:text-green-400 disabled:opacity-50"
60
-
62
+ :loader="saving"
63
+ iconOnly
61
64
  >
62
- <IconCheckOutline class="w-5 h-5" />
63
- </button>
64
- <button
65
+ <IconCheck />
66
+ </Button>
67
+ <Button
65
68
  @click="cancelEdit"
66
69
  :disabled="saving"
67
- class="text-red-600 hover:text-red-700 dark:text-red-500 dark:hover:text-red-400"
70
+ variant="secondary"
71
+ iconOnly
68
72
  >
69
- <IconXOutline class="w-5 h-5" />
70
- </button>
73
+ <IconX />
74
+ </Button>
71
75
  </div>
72
76
  </div>
73
77
  <div v-if="meta?.reviewedCheckboxesFieldName">
74
- <label class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-0 cursor-pointer select-none">
75
- <Checkbox
76
- v-model="reviewed"
77
- :disabled="saving"
78
- />
78
+ <Checkbox
79
+ v-model="reviewed"
80
+ :disabled="saving"
81
+ >
79
82
  {{ t('Translation is reviewed') }}
80
- </label>
83
+ </Checkbox>
81
84
 
82
85
  </div>
83
86
  </div>
@@ -87,13 +90,12 @@
87
90
 
88
91
  <script setup lang="ts">
89
92
  import { ref, Ref, computed, nextTick, onMounted } from 'vue';
90
- import { IconPenSolid, IconCheckOutline, IconXOutline, IconQuestionCircleSolid } from '@iconify-prerendered/vue-flowbite';
93
+ import { IconPencil, IconCheck, IconX, IconCircleCheck, IconCircleHelp } from '@iconify-prerendered/vue-lucide';
91
94
  import { callAdminForthApi } from '@/utils';
92
95
  import { showErrorTost, showSuccesTost } from '@/composables/useFrontendApi';
93
96
  import ColumnValueInputWrapper from '@/components/ColumnValueInputWrapper.vue';
94
97
  import { useI18n } from 'vue-i18n';
95
- import Tooltip from '@/afcl/Tooltip.vue';
96
- import Checkbox from '@/afcl/Checkbox.vue';
98
+ import { Button, Checkbox, Tooltip } from '@/afcl';
97
99
  import { eventBus } from './eventBus'
98
100
 
99
101
  const listCell = ref(null);
@@ -1,24 +1,22 @@
1
1
  <template>
2
- <Input
2
+ <Input
3
3
  ref="input"
4
4
  v-if="shortValue"
5
- class="w-full"
5
+ type="text"
6
+ fullWidth
6
7
  :modelValue="props.record[props.column.name]"
7
- :disabled="props.column.editReadonly"
8
+ :readonly="props.column.editReadonly"
8
9
  @update:modelValue="($event) => emit('update:value', $event)" />
9
- <textarea
10
+ <Textarea
10
11
  v-else
11
12
  ref="textarea"
12
- :disabled="props.column.editReadonly"
13
- class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg block w-full p-2.5
14
- dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary"
15
- :value="props.record[props.column.name]"
16
- @input="($event) => { autoResizeTextArea(); emit('update:value', $event.target.value) }" />
17
-
13
+ :modelValue="props.record[props.column.name]"
14
+ :readonly="props.column.editReadonly"
15
+ @update:modelValue="($event) => { autoResizeTextArea(); emit('update:value', $event) }" />
18
16
  </template>
19
17
 
20
18
  <script setup lang="ts">
21
- import { Input } from "@/afcl";
19
+ import { Input, Textarea } from "@/afcl";
22
20
  import type {
23
21
  AdminForthResourceColumnCommon,
24
22
  AdminForthResourceCommon,
@@ -38,8 +36,8 @@ const props = defineProps<{
38
36
 
39
37
  const emit = defineEmits(["update:value"]);
40
38
 
41
- const input: Ref<HTMLInputElement | HTMLTextAreaElement | null> = ref(null);
42
- const textarea: Ref<HTMLInputElement | HTMLTextAreaElement | null> = ref(null);
39
+ const input = ref<InstanceType<typeof Input> | null>(null);
40
+ const textarea = ref<InstanceType<typeof Textarea> | null>(null);
43
41
 
44
42
  // Auto resize function
45
43
  const autoResizeTextArea = () => {
@@ -47,10 +45,12 @@ const autoResizeTextArea = () => {
47
45
  nextTick(() => {
48
46
  if (!textarea.value) return
49
47
 
48
+ // the textarea is the root element of AFCL Textarea
49
+ const el = textarea.value.$el as HTMLTextAreaElement
50
50
  // Reset the height to shrink if content is removed
51
- textarea.value.style.height = 'auto'
51
+ el.style.height = 'auto'
52
52
  // Set it to the scrollHeight to make it grow
53
- textarea.value.style.height = textarea.value.scrollHeight + 3 + 'px'
53
+ el.style.height = el.scrollHeight + 3 + 'px'
54
54
  })
55
55
  }
56
56
 
@@ -2,18 +2,18 @@
2
2
  <template>
3
3
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-4 my-3 mt-5">
4
4
  <div class="flex items-center space-x-1">
5
- <span class="text-gray-600 dark:text-gray-300">{{ t('Total tokens will be used for translation:') }}</span>
6
- <span class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ new Number(props.job.state?.totalTranslationTokenCost).toLocaleString() || 0 }}</span>
5
+ <span class="text-lightInputIconColor dark:text-darkInputIconColor">{{ t('Total tokens will be used for translation:') }}</span>
6
+ <span class="text-lg font-semibold tabular-nums text-lightInputText dark:text-darkInputText">{{ new Number(props.job.state?.totalTranslationTokenCost).toLocaleString() || 0 }}</span>
7
7
  </div>
8
8
  <div class="flex items-center space-x-1">
9
- <span class="text-gray-600 dark:text-gray-300">{{ t('Total translation token used:') }}</span>
10
- <span class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ new Number(props.job.state?.totalUsedTokens).toLocaleString() || 0 }}</span>
9
+ <span class="text-lightInputIconColor dark:text-darkInputIconColor">{{ t('Total translation token used:') }}</span>
10
+ <span class="text-lg font-semibold tabular-nums text-lightInputText dark:text-darkInputText">{{ new Number(props.job.state?.totalUsedTokens).toLocaleString() || 0 }}</span>
11
11
  </div>
12
12
  </div>
13
13
  <div class="grid grid-cols-3 gap-2">
14
14
  <div
15
15
  v-if="!isFetchingTasks"
16
- class="bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border-gray-200 dark:border-gray-700 transition-all px-2 py-2 rounded-md border max-w-64 w-full flex items-center justify-between gap-2"
16
+ class="bg-lightSecondaryDarken border-lightTableBorder dark:bg-darkSecondaryLighten dark:border-darkTableBorder px-2 py-2 rounded-default border max-w-64 w-full flex items-center justify-between gap-2"
17
17
  v-for="(task, index) in translationTasks"
18
18
  :key="index"
19
19
  >
@@ -21,9 +21,7 @@
21
21
  {{ task.state?.taskName }} to
22
22
  </span>
23
23
  <div class="flex items-center gap-2 shrink-0">
24
- <span class="flag-icon"
25
- :class="`flag-icon-${task.state?.countryCode}`"
26
- ></span>
24
+ <CountryFlag size="sm" :countryCode="task.state?.countryCode" />
27
25
  <component
28
26
  :is="getCustomComponent({file: '@@/plugins/BackgroundJobsPlugin/StateToIcon.vue'})"
29
27
  :status="task.status"
@@ -38,20 +36,22 @@
38
36
  <div v-if="page > 0 || hasNextPage" class="flex items-center justify-center gap-3 mt-4">
39
37
  <Button
40
38
  variant="secondary"
39
+ iconOnly
41
40
  :disabled="page === 0 || isFetchingTasks"
42
41
  @click="loadPage(page - 1)"
43
42
  >
44
- <IconAngleLeftOutline class="w-4 h-4" />
43
+ <IconChevronLeft />
45
44
  </Button>
46
- <span class="text-sm text-gray-600 dark:text-gray-300">
45
+ <span class="text-sm tabular-nums text-lightInputIconColor dark:text-darkInputIconColor">
47
46
  {{ t('Page {page}', { page: page + 1 }) }}
48
47
  </span>
49
48
  <Button
50
49
  variant="secondary"
50
+ iconOnly
51
51
  :disabled="!hasNextPage || isFetchingTasks"
52
52
  @click="loadPage(page + 1)"
53
53
  >
54
- <IconAngleRightOutline class="w-4 h-4" />
54
+ <IconChevronRight />
55
55
  </Button>
56
56
  </div>
57
57
  </template>
@@ -62,8 +62,8 @@ import { useI18n } from 'vue-i18n'
62
62
  import { computed, onMounted, onUnmounted, ref } from 'vue';
63
63
  import websocket from '@/websocket';
64
64
  import { getCustomComponent } from '@/utils';
65
- import { Button, Spinner } from '@/afcl';
66
- import { IconAngleLeftOutline, IconAngleRightOutline } from '@iconify-prerendered/vue-flowbite';
65
+ import { Button, CountryFlag, Spinner } from '@/afcl';
66
+ import { IconChevronLeft, IconChevronRight } from '@iconify-prerendered/vue-lucide';
67
67
 
68
68
  const { t } = useI18n();
69
69
 
@@ -12,9 +12,7 @@
12
12
  "country-list": "^2.4.1",
13
13
  "iso-639-1": "^3.1.5"
14
14
  },
15
- "devDependencies": {
16
- "flag-icon-css": "^4.1.7"
17
- }
15
+ "devDependencies": {}
18
16
  },
19
17
  "node_modules/country-list": {
20
18
  "version": "2.4.1",
@@ -22,14 +20,6 @@
22
20
  "integrity": "sha512-KhVV/UfUV3dSNpsWIqHTQxLpYDKPKz1UwkRjadt+YbX2PRhyCEihEoS5XgB7J7AMXpkicvl+tRHvkNI5wbji/g==",
23
21
  "license": "MIT"
24
22
  },
25
- "node_modules/flag-icon-css": {
26
- "version": "4.1.7",
27
- "resolved": "https://registry.npmjs.org/flag-icon-css/-/flag-icon-css-4.1.7.tgz",
28
- "integrity": "sha512-AFjSU+fv98XbU0vnTQ32vcLj89UEr1MhwDFcooQv14qWJCjg9fGZzfh9BVyDhAhIOZW/pGmJmq38RqpgPaeybQ==",
29
- "deprecated": "The project has been renamed to flag-icons",
30
- "dev": true,
31
- "license": "MIT"
32
- },
33
23
  "node_modules/iso-639-1": {
34
24
  "version": "3.1.5",
35
25
  "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-3.1.5.tgz",
@@ -10,7 +10,6 @@
10
10
  "license": "ISC",
11
11
  "description": "",
12
12
  "devDependencies": {
13
- "flag-icon-css": "^4.1.7"
14
13
  },
15
14
  "dependencies": {
16
15
  "country-list": "^2.4.1",
@@ -14,20 +14,12 @@ importers:
14
14
  iso-639-1:
15
15
  specifier: ^3.1.5
16
16
  version: 3.1.5
17
- devDependencies:
18
- flag-icon-css:
19
- specifier: ^4.1.7
20
- version: 4.1.7
21
17
 
22
18
  packages:
23
19
 
24
20
  country-list@2.4.1:
25
21
  resolution: {integrity: sha512-KhVV/UfUV3dSNpsWIqHTQxLpYDKPKz1UwkRjadt+YbX2PRhyCEihEoS5XgB7J7AMXpkicvl+tRHvkNI5wbji/g==}
26
22
 
27
- flag-icon-css@4.1.7:
28
- resolution: {integrity: sha512-AFjSU+fv98XbU0vnTQ32vcLj89UEr1MhwDFcooQv14qWJCjg9fGZzfh9BVyDhAhIOZW/pGmJmq38RqpgPaeybQ==}
29
- deprecated: The project has been renamed to flag-icons
30
-
31
23
  iso-639-1@3.1.5:
32
24
  resolution: {integrity: sha512-gXkz5+KN7HrG0Q5UGqSMO2qB9AsbEeyLP54kF1YrMsIxmu+g4BdB7rflReZTSTZGpfj8wywu6pfPBCylPIzGQA==}
33
25
  engines: {node: '>=6.0'}
@@ -36,6 +28,4 @@ snapshots:
36
28
 
37
29
  country-list@2.4.1: {}
38
30
 
39
- flag-icon-css@4.1.7: {}
40
-
41
31
  iso-639-1@3.1.5: {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/i18n",
3
- "version": "2.5.1",
3
+ "version": "3.1.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -39,7 +39,7 @@
39
39
  "zod": "^4.3.6"
40
40
  },
41
41
  "peerDependencies": {
42
- "adminforth": "^3.8.2"
42
+ "adminforth": "^4.5.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^22.10.7",