@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 +2 -2
- package/custom/BulkActionButton.vue +27 -35
- package/custom/LanguageInUserMenu.vue +17 -45
- package/custom/LanguageUnderLogin.vue +10 -16
- package/custom/ListCell.vue +28 -26
- package/custom/SingleMultiInput.vue +15 -15
- package/custom/TranslationJobViewComponent.vue +13 -13
- package/custom/package-lock.json +1 -11
- package/custom/package.json +0 -1
- package/custom/pnpm-lock.yaml +0 -10
- package/dist/custom/BulkActionButton.vue +27 -35
- package/dist/custom/LanguageInUserMenu.vue +17 -45
- package/dist/custom/LanguageUnderLogin.vue +10 -16
- package/dist/custom/ListCell.vue +28 -26
- package/dist/custom/SingleMultiInput.vue +15 -15
- package/dist/custom/TranslationJobViewComponent.vue +13 -13
- package/dist/custom/package-lock.json +1 -11
- package/dist/custom/package.json +0 -1
- package/dist/custom/pnpm-lock.yaml +0 -10
- package/package.json +2 -2
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
|
|
24
|
-
total size is
|
|
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: '
|
|
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
|
+
<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 {
|
|
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
|
-
<
|
|
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
|
+
<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
|
-
<
|
|
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
|
+
<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 '
|
|
49
|
-
import {
|
|
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
|
-
<
|
|
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="
|
|
11
|
-
<
|
|
12
|
-
|
|
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="
|
|
21
|
-
<
|
|
22
|
-
|
|
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
|
-
</
|
|
23
|
+
</div>
|
|
29
24
|
</template>
|
|
30
25
|
|
|
31
26
|
<script setup>
|
|
32
|
-
import Select from '@/afcl
|
|
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
|
|
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
|
+
<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
|
-
<
|
|
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>
|
|
@@ -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 {
|
|
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
|
|
package/custom/package-lock.json
CHANGED
|
@@ -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",
|
package/custom/package.json
CHANGED
package/custom/pnpm-lock.yaml
CHANGED
|
@@ -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: '
|
|
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
|
+
<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 {
|
|
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
|
-
<
|
|
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
|
+
<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
|
-
<
|
|
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
|
+
<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 '
|
|
49
|
-
import {
|
|
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
|
-
<
|
|
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="
|
|
11
|
-
<
|
|
12
|
-
|
|
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="
|
|
21
|
-
<
|
|
22
|
-
|
|
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
|
-
</
|
|
23
|
+
</div>
|
|
29
24
|
</template>
|
|
30
25
|
|
|
31
26
|
<script setup>
|
|
32
|
-
import Select from '@/afcl
|
|
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
|
|
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
|
+
<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
|
-
<
|
|
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>
|
|
@@ -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 {
|
|
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",
|
package/dist/custom/package.json
CHANGED
|
@@ -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": "
|
|
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": "^
|
|
42
|
+
"adminforth": "^4.5.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^22.10.7",
|