@adminforth/i18n 2.5.1 → 3.0.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
@@ -6,6 +6,7 @@ sending incremental file list
6
6
  custom/
7
7
  custom/BulkActionButton.vue
8
8
  custom/LanguageEveryPageLoader.vue
9
+ custom/LanguageFlag.vue
9
10
  custom/LanguageInUserMenu.vue
10
11
  custom/LanguageUnderLogin.vue
11
12
  custom/ListCell.vue
@@ -20,5 +21,5 @@ custom/package.json
20
21
  custom/pnpm-lock.yaml
21
22
  custom/tsconfig.json
22
23
 
23
- sent 47,235 bytes received 305 bytes 95,080.00 bytes/sec
24
- total size is 46,083 speedup is 0.97
24
+ sent 45,404 bytes received 324 bytes 91,456.00 bytes/sec
25
+ total size is 44,165 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
+ <LanguageFlag :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,7 +49,7 @@
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
54
  import { Dialog, Button, Checkbox, Spinner } from '@/afcl';
58
55
  import { computed, onMounted, ref, onUnmounted } from 'vue';
@@ -60,7 +57,7 @@
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';
60
+ import LanguageFlag from './LanguageFlag.vue';
64
61
  import websocket from '@/websocket';
65
62
  import { useFiltersStore } from '@/stores/filters';
66
63
  import { useBackgroundJobApi } from '@/custom/plugins/BackgroundJobsPlugin/useBackgroundJobApi.ts';
@@ -127,10 +124,6 @@
127
124
  }
128
125
  }
129
126
 
130
- function toggleLanguage(lang: string) {
131
- checkedLanguages.value[lang] = !checkedLanguages.value[lang];
132
- }
133
-
134
127
  async function runTranslation() {
135
128
  isLoading.value = true;
136
129
 
@@ -0,0 +1,16 @@
1
+ <template>
2
+ <!-- only the flag-icon-<code> image class: the base flag-icon class would fight the size -->
3
+ <span
4
+ class="af-language-flag inline-block w-4 h-3 shrink-0 rounded-[2px] bg-cover bg-center ring-1 ring-black/10 dark:ring-white/10"
5
+ :class="`flag-icon-${countryCode}`"
6
+ aria-hidden="true"
7
+ />
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import 'flag-icon-css/css/flag-icons.min.css';
12
+
13
+ defineProps<{
14
+ countryCode: string,
15
+ }>();
16
+ </script>
@@ -1,52 +1,26 @@
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
+ <LanguageFlag :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
+ <LanguageFlag :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 { DropdownMenuAccordion, DropdownMenuItem } from '@/afcl';
22
+ import { USER_MENU_ITEM_CLASSES } from '@/utils/userMenu';
23
+ import LanguageFlag from './LanguageFlag.vue';
50
24
 
51
25
  import { setLang, getLocalLang, setLocalLang, getLanguageOptions } from './langCommon';
52
26
 
@@ -55,7 +29,6 @@ import { useI18n } from 'vue-i18n';
55
29
 
56
30
  const { setLocaleMessage, locale } = useI18n();
57
31
 
58
- const showDropdown = ref(false);
59
32
  const props = defineProps(['meta', 'resource']);
60
33
 
61
34
  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,25 @@
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
+ <LanguageFlag :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
+ <LanguageFlag :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
27
  import Select from '@/afcl/Select.vue';
33
- import 'flag-icon-css/css/flag-icons.min.css';
28
+ import LanguageFlag from './LanguageFlag.vue';
34
29
  import { setLang, getLocalLang, getLanguageOptions } from './langCommon';
35
30
  import { useCoreStore } from '@/stores/core';
36
31
 
@@ -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
+ <LanguageFlag :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>
@@ -63,7 +63,8 @@ import { computed, onMounted, onUnmounted, ref } from 'vue';
63
63
  import websocket from '@/websocket';
64
64
  import { getCustomComponent } from '@/utils';
65
65
  import { Button, Spinner } from '@/afcl';
66
- import { IconAngleLeftOutline, IconAngleRightOutline } from '@iconify-prerendered/vue-flowbite';
66
+ import { IconChevronLeft, IconChevronRight } from '@iconify-prerendered/vue-lucide';
67
+ import LanguageFlag from './LanguageFlag.vue';
67
68
 
68
69
  const { t } = useI18n();
69
70
 
@@ -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
+ <LanguageFlag :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,7 +49,7 @@
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
54
  import { Dialog, Button, Checkbox, Spinner } from '@/afcl';
58
55
  import { computed, onMounted, ref, onUnmounted } from 'vue';
@@ -60,7 +57,7 @@
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';
60
+ import LanguageFlag from './LanguageFlag.vue';
64
61
  import websocket from '@/websocket';
65
62
  import { useFiltersStore } from '@/stores/filters';
66
63
  import { useBackgroundJobApi } from '@/custom/plugins/BackgroundJobsPlugin/useBackgroundJobApi.ts';
@@ -127,10 +124,6 @@
127
124
  }
128
125
  }
129
126
 
130
- function toggleLanguage(lang: string) {
131
- checkedLanguages.value[lang] = !checkedLanguages.value[lang];
132
- }
133
-
134
127
  async function runTranslation() {
135
128
  isLoading.value = true;
136
129
 
@@ -0,0 +1,16 @@
1
+ <template>
2
+ <!-- only the flag-icon-<code> image class: the base flag-icon class would fight the size -->
3
+ <span
4
+ class="af-language-flag inline-block w-4 h-3 shrink-0 rounded-[2px] bg-cover bg-center ring-1 ring-black/10 dark:ring-white/10"
5
+ :class="`flag-icon-${countryCode}`"
6
+ aria-hidden="true"
7
+ />
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import 'flag-icon-css/css/flag-icons.min.css';
12
+
13
+ defineProps<{
14
+ countryCode: string,
15
+ }>();
16
+ </script>
@@ -1,52 +1,26 @@
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
+ <LanguageFlag :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
+ <LanguageFlag :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 { DropdownMenuAccordion, DropdownMenuItem } from '@/afcl';
22
+ import { USER_MENU_ITEM_CLASSES } from '@/utils/userMenu';
23
+ import LanguageFlag from './LanguageFlag.vue';
50
24
 
51
25
  import { setLang, getLocalLang, setLocalLang, getLanguageOptions } from './langCommon';
52
26
 
@@ -55,7 +29,6 @@ import { useI18n } from 'vue-i18n';
55
29
 
56
30
  const { setLocaleMessage, locale } = useI18n();
57
31
 
58
- const showDropdown = ref(false);
59
32
  const props = defineProps(['meta', 'resource']);
60
33
 
61
34
  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,25 @@
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
+ <LanguageFlag :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
+ <LanguageFlag :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
27
  import Select from '@/afcl/Select.vue';
33
- import 'flag-icon-css/css/flag-icons.min.css';
28
+ import LanguageFlag from './LanguageFlag.vue';
34
29
  import { setLang, getLocalLang, getLanguageOptions } from './langCommon';
35
30
  import { useCoreStore } from '@/stores/core';
36
31
 
@@ -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
+ <LanguageFlag :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>
@@ -63,7 +63,8 @@ import { computed, onMounted, onUnmounted, ref } from 'vue';
63
63
  import websocket from '@/websocket';
64
64
  import { getCustomComponent } from '@/utils';
65
65
  import { Button, Spinner } from '@/afcl';
66
- import { IconAngleLeftOutline, IconAngleRightOutline } from '@iconify-prerendered/vue-flowbite';
66
+ import { IconChevronLeft, IconChevronRight } from '@iconify-prerendered/vue-lucide';
67
+ import LanguageFlag from './LanguageFlag.vue';
67
68
 
68
69
  const { t } = useI18n();
69
70
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/i18n",
3
- "version": "2.5.1",
3
+ "version": "3.0.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.0.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^22.10.7",