@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 +3 -2
- package/custom/BulkActionButton.vue +27 -34
- package/custom/LanguageFlag.vue +16 -0
- package/custom/LanguageInUserMenu.vue +18 -45
- package/custom/LanguageUnderLogin.vue +10 -15
- package/custom/ListCell.vue +28 -26
- package/custom/SingleMultiInput.vue +15 -15
- package/custom/TranslationJobViewComponent.vue +13 -12
- package/dist/custom/BulkActionButton.vue +27 -34
- package/dist/custom/LanguageFlag.vue +16 -0
- package/dist/custom/LanguageInUserMenu.vue +18 -45
- package/dist/custom/LanguageUnderLogin.vue +10 -15
- package/dist/custom/ListCell.vue +28 -26
- package/dist/custom/SingleMultiInput.vue +15 -15
- package/dist/custom/TranslationJobViewComponent.vue +13 -12
- package/package.json +2 -2
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
|
|
24
|
-
total size is
|
|
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: '
|
|
8
|
-
onclick: async (dialog) => { await runTranslation(); dialog.hide(); } ,
|
|
7
|
+
label: t('Close'),
|
|
9
8
|
options: {
|
|
10
|
-
|
|
11
|
-
}
|
|
9
|
+
variant: 'secondary'
|
|
10
|
+
},
|
|
11
|
+
onclick: (dialog) => dialog.hide()
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
|
-
label: '
|
|
14
|
+
label: t('Translate'),
|
|
15
|
+
onclick: async (dialog) => { await runTranslation(); dialog.hide(); } ,
|
|
15
16
|
options: {
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
onclick: (dialog) => dialog.hide()
|
|
17
|
+
disabled: noneChecked
|
|
18
|
+
}
|
|
19
19
|
},
|
|
20
20
|
]"
|
|
21
21
|
>
|
|
22
22
|
<template #trigger>
|
|
23
|
-
<
|
|
24
|
-
<
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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-
|
|
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-
|
|
41
|
-
<
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 {
|
|
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 '
|
|
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
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
<
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 '
|
|
49
|
-
import {
|
|
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
|
-
<
|
|
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="
|
|
11
|
-
<
|
|
12
|
-
|
|
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="
|
|
21
|
-
<
|
|
22
|
-
|
|
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
|
-
</
|
|
23
|
+
</div>
|
|
29
24
|
</template>
|
|
30
25
|
|
|
31
26
|
<script setup>
|
|
32
27
|
import Select from '@/afcl/Select.vue';
|
|
33
|
-
import '
|
|
28
|
+
import LanguageFlag from './LanguageFlag.vue';
|
|
34
29
|
import { setLang, getLocalLang, getLanguageOptions } from './langCommon';
|
|
35
30
|
import { useCoreStore } from '@/stores/core';
|
|
36
31
|
|
package/custom/ListCell.vue
CHANGED
|
@@ -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
|
-
<
|
|
18
|
+
<IconCircleCheck
|
|
19
19
|
v-if="record[meta?.reviewedCheckboxesFieldName]?.[props.column.name]"
|
|
20
|
-
class="w-
|
|
20
|
+
class="w-4 h-4 text-lightSuccess dark:text-darkSuccess"
|
|
21
21
|
/>
|
|
22
|
-
<
|
|
22
|
+
<IconCircleHelp
|
|
23
23
|
v-else
|
|
24
|
-
class="w-
|
|
24
|
+
class="w-4 h-4 text-lightWarning dark:text-darkWarning"
|
|
25
25
|
/>
|
|
26
26
|
</Tooltip>
|
|
27
27
|
</span>
|
|
28
28
|
|
|
29
|
-
<
|
|
29
|
+
<Button
|
|
30
30
|
v-if="!column.editReadonly"
|
|
31
31
|
@click="startEdit"
|
|
32
|
-
|
|
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
|
-
<
|
|
35
|
-
</
|
|
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
|
-
<
|
|
59
|
+
<Button
|
|
57
60
|
@click="saveEdit"
|
|
58
61
|
:disabled="saving || (originalReviewed === reviewed && originalValue === currentValues[props.column.name])"
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
:loader="saving"
|
|
63
|
+
iconOnly
|
|
61
64
|
>
|
|
62
|
-
<
|
|
63
|
-
</
|
|
64
|
-
<
|
|
65
|
+
<IconCheck />
|
|
66
|
+
</Button>
|
|
67
|
+
<Button
|
|
65
68
|
@click="cancelEdit"
|
|
66
69
|
:disabled="saving"
|
|
67
|
-
|
|
70
|
+
variant="secondary"
|
|
71
|
+
iconOnly
|
|
68
72
|
>
|
|
69
|
-
<
|
|
70
|
-
</
|
|
73
|
+
<IconX />
|
|
74
|
+
</Button>
|
|
71
75
|
</div>
|
|
72
76
|
</div>
|
|
73
77
|
<div v-if="meta?.reviewedCheckboxesFieldName">
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
/>
|
|
78
|
+
<Checkbox
|
|
79
|
+
v-model="reviewed"
|
|
80
|
+
:disabled="saving"
|
|
81
|
+
>
|
|
79
82
|
{{ t('Translation is reviewed') }}
|
|
80
|
-
</
|
|
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 {
|
|
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
|
|
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
|
-
|
|
5
|
+
type="text"
|
|
6
|
+
fullWidth
|
|
6
7
|
:modelValue="props.record[props.column.name]"
|
|
7
|
-
:
|
|
8
|
+
:readonly="props.column.editReadonly"
|
|
8
9
|
@update:modelValue="($event) => emit('update:value', $event)" />
|
|
9
|
-
<
|
|
10
|
+
<Textarea
|
|
10
11
|
v-else
|
|
11
12
|
ref="textarea"
|
|
12
|
-
:
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
42
|
-
const textarea
|
|
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
|
-
|
|
51
|
+
el.style.height = 'auto'
|
|
52
52
|
// Set it to the scrollHeight to make it grow
|
|
53
|
-
|
|
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-
|
|
6
|
-
<span class="text-lg font-semibold text-
|
|
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-
|
|
10
|
-
<span class="text-lg font-semibold text-
|
|
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-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
43
|
+
<IconChevronLeft />
|
|
45
44
|
</Button>
|
|
46
|
-
<span class="text-sm text-
|
|
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
|
-
<
|
|
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 {
|
|
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: '
|
|
8
|
-
onclick: async (dialog) => { await runTranslation(); dialog.hide(); } ,
|
|
7
|
+
label: t('Close'),
|
|
9
8
|
options: {
|
|
10
|
-
|
|
11
|
-
}
|
|
9
|
+
variant: 'secondary'
|
|
10
|
+
},
|
|
11
|
+
onclick: (dialog) => dialog.hide()
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
|
-
label: '
|
|
14
|
+
label: t('Translate'),
|
|
15
|
+
onclick: async (dialog) => { await runTranslation(); dialog.hide(); } ,
|
|
15
16
|
options: {
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
onclick: (dialog) => dialog.hide()
|
|
17
|
+
disabled: noneChecked
|
|
18
|
+
}
|
|
19
19
|
},
|
|
20
20
|
]"
|
|
21
21
|
>
|
|
22
22
|
<template #trigger>
|
|
23
|
-
<
|
|
24
|
-
<
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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-
|
|
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-
|
|
41
|
-
<
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 {
|
|
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 '
|
|
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
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
<
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 '
|
|
49
|
-
import {
|
|
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
|
-
<
|
|
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="
|
|
11
|
-
<
|
|
12
|
-
|
|
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="
|
|
21
|
-
<
|
|
22
|
-
|
|
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
|
-
</
|
|
23
|
+
</div>
|
|
29
24
|
</template>
|
|
30
25
|
|
|
31
26
|
<script setup>
|
|
32
27
|
import Select from '@/afcl/Select.vue';
|
|
33
|
-
import '
|
|
28
|
+
import LanguageFlag from './LanguageFlag.vue';
|
|
34
29
|
import { setLang, getLocalLang, getLanguageOptions } from './langCommon';
|
|
35
30
|
import { useCoreStore } from '@/stores/core';
|
|
36
31
|
|
package/dist/custom/ListCell.vue
CHANGED
|
@@ -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
|
-
<
|
|
18
|
+
<IconCircleCheck
|
|
19
19
|
v-if="record[meta?.reviewedCheckboxesFieldName]?.[props.column.name]"
|
|
20
|
-
class="w-
|
|
20
|
+
class="w-4 h-4 text-lightSuccess dark:text-darkSuccess"
|
|
21
21
|
/>
|
|
22
|
-
<
|
|
22
|
+
<IconCircleHelp
|
|
23
23
|
v-else
|
|
24
|
-
class="w-
|
|
24
|
+
class="w-4 h-4 text-lightWarning dark:text-darkWarning"
|
|
25
25
|
/>
|
|
26
26
|
</Tooltip>
|
|
27
27
|
</span>
|
|
28
28
|
|
|
29
|
-
<
|
|
29
|
+
<Button
|
|
30
30
|
v-if="!column.editReadonly"
|
|
31
31
|
@click="startEdit"
|
|
32
|
-
|
|
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
|
-
<
|
|
35
|
-
</
|
|
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
|
-
<
|
|
59
|
+
<Button
|
|
57
60
|
@click="saveEdit"
|
|
58
61
|
:disabled="saving || (originalReviewed === reviewed && originalValue === currentValues[props.column.name])"
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
:loader="saving"
|
|
63
|
+
iconOnly
|
|
61
64
|
>
|
|
62
|
-
<
|
|
63
|
-
</
|
|
64
|
-
<
|
|
65
|
+
<IconCheck />
|
|
66
|
+
</Button>
|
|
67
|
+
<Button
|
|
65
68
|
@click="cancelEdit"
|
|
66
69
|
:disabled="saving"
|
|
67
|
-
|
|
70
|
+
variant="secondary"
|
|
71
|
+
iconOnly
|
|
68
72
|
>
|
|
69
|
-
<
|
|
70
|
-
</
|
|
73
|
+
<IconX />
|
|
74
|
+
</Button>
|
|
71
75
|
</div>
|
|
72
76
|
</div>
|
|
73
77
|
<div v-if="meta?.reviewedCheckboxesFieldName">
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
/>
|
|
78
|
+
<Checkbox
|
|
79
|
+
v-model="reviewed"
|
|
80
|
+
:disabled="saving"
|
|
81
|
+
>
|
|
79
82
|
{{ t('Translation is reviewed') }}
|
|
80
|
-
</
|
|
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 {
|
|
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
|
|
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
|
-
|
|
5
|
+
type="text"
|
|
6
|
+
fullWidth
|
|
6
7
|
:modelValue="props.record[props.column.name]"
|
|
7
|
-
:
|
|
8
|
+
:readonly="props.column.editReadonly"
|
|
8
9
|
@update:modelValue="($event) => emit('update:value', $event)" />
|
|
9
|
-
<
|
|
10
|
+
<Textarea
|
|
10
11
|
v-else
|
|
11
12
|
ref="textarea"
|
|
12
|
-
:
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
42
|
-
const textarea
|
|
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
|
-
|
|
51
|
+
el.style.height = 'auto'
|
|
52
52
|
// Set it to the scrollHeight to make it grow
|
|
53
|
-
|
|
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-
|
|
6
|
-
<span class="text-lg font-semibold text-
|
|
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-
|
|
10
|
-
<span class="text-lg font-semibold text-
|
|
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-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
43
|
+
<IconChevronLeft />
|
|
45
44
|
</Button>
|
|
46
|
-
<span class="text-sm text-
|
|
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
|
-
<
|
|
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 {
|
|
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": "
|
|
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": "^
|
|
42
|
+
"adminforth": "^4.0.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^22.10.7",
|