@bagelink/vue 1.9.51 → 1.9.56
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/dist/components/AddressSearch.vue.d.ts.map +1 -1
- package/dist/components/Btn.vue.d.ts +2 -2
- package/dist/components/Btn.vue.d.ts.map +1 -1
- package/dist/components/Filter.vue.d.ts +0 -1
- package/dist/components/Filter.vue.d.ts.map +1 -1
- package/dist/components/ImportData.vue.d.ts.map +1 -1
- package/dist/components/ModalConfirm.vue.d.ts.map +1 -1
- package/dist/components/Spreadsheet/Index.vue.d.ts.map +1 -1
- package/dist/components/form/FieldArray.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/ColorInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/DateInput.vue.d.ts +1 -1
- package/dist/components/form/inputs/DateInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/DatePicker.vue.d.ts +1 -1
- package/dist/components/form/inputs/DatePicker.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/EmailInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/PasswordInput.vue.d.ts +1 -1
- package/dist/components/form/inputs/PasswordInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RadioPillsInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts +0 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/TelInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts.map +1 -1
- package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
- package/dist/dialog/DialogConfirm.vue.d.ts +0 -3
- package/dist/dialog/DialogConfirm.vue.d.ts.map +1 -1
- package/dist/dialog/DialogForm.vue.d.ts.map +1 -1
- package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
- package/dist/form-flow/MultiStepForm.vue.d.ts.map +1 -1
- package/dist/i18n/index.d.ts +995 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/index.cjs +135 -130
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +15388 -11464
- package/dist/plugins/bagel.d.ts +5 -7
- package/dist/plugins/bagel.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/dist/utils/calendar/dateUtils.d.ts +6 -5
- package/dist/utils/calendar/dateUtils.d.ts.map +1 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/lang.d.ts +6 -7
- package/dist/utils/lang.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/components/AddressSearch.vue +5 -2
- package/src/components/Btn.vue +8 -6
- package/src/components/Filter.vue +37 -76
- package/src/components/ImportData.vue +35 -29
- package/src/components/ModalConfirm.vue +9 -7
- package/src/components/Spreadsheet/Index.vue +35 -60
- package/src/components/calendar/views/AgendaView.vue +1 -1
- package/src/components/calendar/views/DayView.vue +3 -3
- package/src/components/calendar/views/MonthView.vue +1 -1
- package/src/components/calendar/views/WeekView.vue +6 -6
- package/src/components/form/FieldArray.vue +4 -2
- package/src/components/form/inputs/ColorInput.vue +18 -12
- package/src/components/form/inputs/DateInput.vue +8 -5
- package/src/components/form/inputs/EmailInput.vue +9 -7
- package/src/components/form/inputs/PasswordInput.vue +17 -15
- package/src/components/form/inputs/RadioPillsInput.vue +5 -3
- package/src/components/form/inputs/SelectInput.vue +13 -11
- package/src/components/form/inputs/TelInput.vue +11 -9
- package/src/components/form/inputs/Upload/UploadInput.vue +7 -5
- package/src/components/lightbox/Lightbox.vue +4 -1
- package/src/dialog/DialogConfirm.vue +11 -6
- package/src/dialog/DialogForm.vue +9 -9
- package/src/form-flow/FormFlow.vue +7 -15
- package/src/form-flow/MultiStepForm.vue +16 -12
- package/src/i18n/index.ts +136 -0
- package/src/i18n/locales/en.json +153 -0
- package/src/i18n/locales/es.json +153 -0
- package/src/i18n/locales/fr.json +153 -0
- package/src/i18n/locales/he.json +174 -0
- package/src/i18n/locales/it.json +153 -0
- package/src/i18n/locales/ru.json +153 -0
- package/src/index.ts +11 -5
- package/src/plugins/bagel.ts +14 -26
- package/src/styles/text.css +2 -1
- package/src/utils/calendar/dateUtils.ts +108 -63
- package/src/utils/index.ts +0 -2
- package/src/utils/lang.ts +0 -46
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { UploadInputProps } from '@bagelink/vue'
|
|
3
|
-
import { Btn, IMAGE_FORMATS_REGEXP, Icon, Card, Image, pathKeyToURL, Loading } from '@bagelink/vue'
|
|
3
|
+
import { Btn, IMAGE_FORMATS_REGEXP, Icon, Card, Image, pathKeyToURL, Loading, useI18n } from '@bagelink/vue'
|
|
4
4
|
import { watch, ref } from 'vue'
|
|
5
5
|
|
|
6
6
|
import { useFileUpload } from './useFileUpload'
|
|
@@ -15,6 +15,8 @@ const props = withDefaults(defineProps<UploadInputProps & { showIcon?: boolean,
|
|
|
15
15
|
|
|
16
16
|
const emit = defineEmits(['update:modelValue', 'addFileStart'])
|
|
17
17
|
|
|
18
|
+
const { $t } = useI18n()
|
|
19
|
+
|
|
18
20
|
const {
|
|
19
21
|
fileQueue,
|
|
20
22
|
pathKeys,
|
|
@@ -78,13 +80,13 @@ function fileName(pathKey: string) {
|
|
|
78
80
|
>
|
|
79
81
|
<Btn
|
|
80
82
|
v-if="!pathKeys.length && !fileQueue.length" class="px-1-5" icon="upload" outline
|
|
81
|
-
:value="btnPlaceholder || '
|
|
83
|
+
:value="btnPlaceholder || $t('upload.upload')" @click="browse()"
|
|
82
84
|
/>
|
|
83
85
|
<!-- Loading state during upload -->
|
|
84
86
|
<div v-if="fileQueue.length > 0" class="flex align-items-center gap-1 p-05">
|
|
85
87
|
<Loading size="20" />
|
|
86
88
|
<span class="txt-gray txt-12">
|
|
87
|
-
|
|
89
|
+
{{ $t('upload.uploading', { count: fileQueue.length, plural: fileQueue.length > 1 ? 's' : '' }) }}
|
|
88
90
|
</span>
|
|
89
91
|
</div>
|
|
90
92
|
|
|
@@ -111,7 +113,7 @@ function fileName(pathKey: string) {
|
|
|
111
113
|
</template>
|
|
112
114
|
|
|
113
115
|
<span v-if="!pathKeys.length && !fileQueue.length" class="txt-gray txt-12">
|
|
114
|
-
{{ noFilePlaceholder || '
|
|
116
|
+
{{ noFilePlaceholder || $t('upload.noFile') }}
|
|
115
117
|
</span>
|
|
116
118
|
</Card>
|
|
117
119
|
|
|
@@ -208,7 +210,7 @@ function fileName(pathKey: string) {
|
|
|
208
210
|
<p class="p-1 flex column hover fileUploadPlaceHolder justify-content-center mb-05 ">
|
|
209
211
|
<Icon v-if="showIcon" :name="icon" class="user-select-none" />
|
|
210
212
|
<span class=" pretty balance user-select-none ">
|
|
211
|
-
{{ dropPlaceholder || '
|
|
213
|
+
{{ dropPlaceholder || $t('upload.dropPlaceholder') }}
|
|
212
214
|
</span>
|
|
213
215
|
</p>
|
|
214
216
|
</slot>
|
|
@@ -3,6 +3,9 @@ import type { LightboxItem } from './lightbox.types'
|
|
|
3
3
|
|
|
4
4
|
import { BglVideo, Btn, Icon, Zoomer, Image, normalizeURL, Swiper, downloadFile } from '@bagelink/vue'
|
|
5
5
|
import { computed, ref, watch } from 'vue'
|
|
6
|
+
import { useI18n } from '../../i18n'
|
|
7
|
+
|
|
8
|
+
const { $t } = useI18n()
|
|
6
9
|
|
|
7
10
|
const isOpen = ref(false)
|
|
8
11
|
const group = ref<LightboxItem[]>([])
|
|
@@ -79,7 +82,7 @@ defineExpose({ open, close })
|
|
|
79
82
|
</div>
|
|
80
83
|
<Btn
|
|
81
84
|
v-if="currentItem?.openFile && currentItem?.src" class="color-white" round thin flat
|
|
82
|
-
iconEnd="arrow_outward" value="
|
|
85
|
+
iconEnd="arrow_outward" value="$t:lightbox.openFile" :href="currentItem?.src" target="_blank"
|
|
83
86
|
/>
|
|
84
87
|
<Btn
|
|
85
88
|
v-if="currentItem?.download && currentItem?.src" class="color-white" round thin flat
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* DialogConfirm - Simple confirmation dialog
|
|
4
4
|
*/
|
|
5
5
|
import type { DialogWidth, DialogPosition } from './dialogTypes'
|
|
6
|
+
import { useI18n } from '@bagelink/vue'
|
|
7
|
+
import { computed } from 'vue'
|
|
6
8
|
import Btn from '../components/Btn.vue'
|
|
7
9
|
import Dialog from './Dialog.vue'
|
|
8
10
|
|
|
@@ -17,12 +19,9 @@ const props = withDefaults(defineProps<{
|
|
|
17
19
|
cancelText?: string
|
|
18
20
|
confirmColor?: 'primary' | 'red' | 'green' | 'blue'
|
|
19
21
|
}>(), {
|
|
20
|
-
title: 'Confirm',
|
|
21
22
|
width: 's',
|
|
22
23
|
position: 'center',
|
|
23
24
|
dismissable: true,
|
|
24
|
-
confirmText: 'Confirm',
|
|
25
|
-
cancelText: 'Cancel',
|
|
26
25
|
confirmColor: 'primary'
|
|
27
26
|
})
|
|
28
27
|
|
|
@@ -31,6 +30,12 @@ const emit = defineEmits<{
|
|
|
31
30
|
'resolve': [confirmed: boolean]
|
|
32
31
|
}>()
|
|
33
32
|
|
|
33
|
+
const { $t } = useI18n()
|
|
34
|
+
|
|
35
|
+
const resolvedTitle = computed(() => props.title || $t('modalConfirm.title'))
|
|
36
|
+
const resolvedConfirmText = computed(() => props.confirmText || $t('modalConfirm.confirm'))
|
|
37
|
+
const resolvedCancelText = computed(() => props.cancelText || $t('modalConfirm.cancel'))
|
|
38
|
+
|
|
34
39
|
function handleConfirm() {
|
|
35
40
|
emit('resolve', true)
|
|
36
41
|
emit('update:open', false)
|
|
@@ -48,7 +53,7 @@ function handleClose() {
|
|
|
48
53
|
|
|
49
54
|
<template>
|
|
50
55
|
<Dialog
|
|
51
|
-
:open="open" :title="
|
|
56
|
+
:open="open" :title="resolvedTitle" :width="width" :position="position" :dismissable="dismissable"
|
|
52
57
|
@update:open="$emit('update:open', $event)" @close="handleClose"
|
|
53
58
|
>
|
|
54
59
|
<p>
|
|
@@ -57,10 +62,10 @@ function handleClose() {
|
|
|
57
62
|
|
|
58
63
|
<template #footer>
|
|
59
64
|
<Btn flat @click="handleCancel">
|
|
60
|
-
{{
|
|
65
|
+
{{ resolvedCancelText }}
|
|
61
66
|
</Btn>
|
|
62
67
|
<Btn :color="confirmColor" @click="handleConfirm">
|
|
63
|
-
{{
|
|
68
|
+
{{ resolvedConfirmText }}
|
|
64
69
|
</Btn>
|
|
65
70
|
</template>
|
|
66
71
|
</Dialog>
|
|
@@ -10,6 +10,7 @@ import type { DialogWidth, DialogPosition, DialogAction } from './dialogTypes'
|
|
|
10
10
|
import { ref, computed } from 'vue'
|
|
11
11
|
import Btn from '../components/Btn.vue'
|
|
12
12
|
import FormFlow from '../form-flow/FormFlow.vue'
|
|
13
|
+
import { useI18n } from '../i18n'
|
|
13
14
|
import Dialog from './Dialog.vue'
|
|
14
15
|
|
|
15
16
|
const props = withDefaults(defineProps<{
|
|
@@ -29,9 +30,6 @@ const props = withDefaults(defineProps<{
|
|
|
29
30
|
width: 'm',
|
|
30
31
|
position: 'center',
|
|
31
32
|
dismissable: true,
|
|
32
|
-
submitText: 'Save',
|
|
33
|
-
cancelText: 'Cancel',
|
|
34
|
-
deleteText: 'Delete'
|
|
35
33
|
})
|
|
36
34
|
|
|
37
35
|
const emit = defineEmits<{
|
|
@@ -39,6 +37,8 @@ const emit = defineEmits<{
|
|
|
39
37
|
'resolve': [result: { action: DialogAction, data: T }]
|
|
40
38
|
}>()
|
|
41
39
|
|
|
40
|
+
const { $t } = useI18n()
|
|
41
|
+
|
|
42
42
|
// Form state - use Record<string, any> internally for FormFlow compatibility
|
|
43
43
|
const formData = ref<Record<string, any>>({ ...(props.modelValue || {}) })
|
|
44
44
|
const isSubmitting = ref(false)
|
|
@@ -55,7 +55,7 @@ async function handleSubmit() {
|
|
|
55
55
|
|
|
56
56
|
// Validate form before submitting
|
|
57
57
|
if (!formFlowRef.value?.validate()) {
|
|
58
|
-
error.value = '
|
|
58
|
+
error.value = $t('dialogForm.validationError')
|
|
59
59
|
return
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -69,7 +69,7 @@ async function handleSubmit() {
|
|
|
69
69
|
emit('resolve', { action: 'submit', data: formData.value as T })
|
|
70
70
|
emit('update:open', false)
|
|
71
71
|
} catch (err: unknown) {
|
|
72
|
-
error.value = err instanceof Error ? err.message : '
|
|
72
|
+
error.value = err instanceof Error ? err.message : $t('dialogForm.errorOccurred')
|
|
73
73
|
} finally {
|
|
74
74
|
isSubmitting.value = false
|
|
75
75
|
}
|
|
@@ -86,7 +86,7 @@ async function handleDelete() {
|
|
|
86
86
|
emit('resolve', { action: 'delete', data: formData.value as T })
|
|
87
87
|
emit('update:open', false)
|
|
88
88
|
} catch (err: unknown) {
|
|
89
|
-
error.value = err instanceof Error ? err.message : '
|
|
89
|
+
error.value = err instanceof Error ? err.message : $t('dialogForm.errorOccurred')
|
|
90
90
|
} finally {
|
|
91
91
|
isDeleting.value = false
|
|
92
92
|
}
|
|
@@ -124,19 +124,19 @@ function handleClose() {
|
|
|
124
124
|
v-if="hasDeleteHandler" color="red" flat :loading="isDeleting" :disabled="isSubmitting" class="me-auto"
|
|
125
125
|
@click="handleDelete"
|
|
126
126
|
>
|
|
127
|
-
{{ deleteText }}
|
|
127
|
+
{{ deleteText || $t('dialogForm.delete') }}
|
|
128
128
|
</Btn>
|
|
129
129
|
|
|
130
130
|
<div class="flex-grow-1" />
|
|
131
131
|
|
|
132
132
|
<!-- Cancel button -->
|
|
133
133
|
<Btn flat :disabled="isSubmitting || isDeleting" @click="handleCancel">
|
|
134
|
-
{{ cancelText }}
|
|
134
|
+
{{ cancelText || $t('dialogForm.cancel') }}
|
|
135
135
|
</Btn>
|
|
136
136
|
|
|
137
137
|
<!-- Submit button -->
|
|
138
138
|
<Btn color="primary" :loading="isSubmitting" :disabled="isDeleting" @click="handleSubmit">
|
|
139
|
-
{{ submitText }}
|
|
139
|
+
{{ submitText || $t('dialogForm.save') }}
|
|
140
140
|
</Btn>
|
|
141
141
|
</template>
|
|
142
142
|
</Dialog>
|
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts" generic="T extends Record<string, any>">
|
|
2
|
-
/**
|
|
3
|
-
* FormFlow - Single-step form renderer
|
|
4
|
-
*
|
|
5
|
-
* Renders forms from schema definitions with automatic:
|
|
6
|
-
* - Field rendering based on type
|
|
7
|
-
* - Conditional visibility (.if() clauses)
|
|
8
|
-
* - Validation (.validate() chains)
|
|
9
|
-
* - Layout (.row() grouping)
|
|
10
|
-
* - Custom component/slot rendering
|
|
11
|
-
* - Nested object support (dot notation)
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
2
|
import type { SchemaDefinition, FieldBuilder } from './form-flow'
|
|
15
3
|
|
|
16
4
|
import { computed, ref, watch } from 'vue'
|
|
17
|
-
|
|
18
5
|
import ArrayInput from '../components/form/inputs/ArrayInput.vue'
|
|
6
|
+
|
|
19
7
|
import CheckInput from '../components/form/inputs/CheckInput.vue'
|
|
20
8
|
import CodeEditor from '../components/form/inputs/CodeEditor/Index.vue'
|
|
21
9
|
import DateInput from '../components/form/inputs/DateInput.vue'
|
|
@@ -29,6 +17,7 @@ import TelInput from '../components/form/inputs/TelInput.vue'
|
|
|
29
17
|
import TextInput from '../components/form/inputs/TextInput.vue'
|
|
30
18
|
import ToggleInput from '../components/form/inputs/ToggleInput.vue'
|
|
31
19
|
import UploadInput from '../components/form/inputs/Upload/UploadInput.vue'
|
|
20
|
+
import { useI18n } from '../i18n'
|
|
32
21
|
|
|
33
22
|
import { parseQuery } from '../utils/queryFilter'
|
|
34
23
|
|
|
@@ -41,11 +30,14 @@ export interface Props<T extends Record<string, any> = Record<string, any>> {
|
|
|
41
30
|
}
|
|
42
31
|
|
|
43
32
|
const props = defineProps<Props<T>>()
|
|
33
|
+
|
|
44
34
|
const emit = defineEmits<{
|
|
45
35
|
'update:modelValue': [value: T]
|
|
46
36
|
'submit': [value: T]
|
|
47
37
|
}>()
|
|
48
38
|
|
|
39
|
+
const { $t } = useI18n()
|
|
40
|
+
|
|
49
41
|
// Get value from nested path (e.g., 'meta.preferences.theme')
|
|
50
42
|
function getNestedValue(obj: any, path: string): any {
|
|
51
43
|
const keys = path.split('.')
|
|
@@ -224,7 +216,7 @@ function validateField(key: string, field: FieldBuilder): boolean {
|
|
|
224
216
|
if (field._config.required === true) {
|
|
225
217
|
const isEmpty = value == null || value === '' || (Array.isArray(value) && value.length === 0)
|
|
226
218
|
if (isEmpty) {
|
|
227
|
-
validationErrors.value = { ...validationErrors.value, [key]: field._config.requiredMessage || '
|
|
219
|
+
validationErrors.value = { ...validationErrors.value, [key]: field._config.requiredMessage || $t('form.required') }
|
|
228
220
|
return false
|
|
229
221
|
}
|
|
230
222
|
}
|
|
@@ -360,7 +352,7 @@ function validateAll(): boolean {
|
|
|
360
352
|
if (field._config.required === true) {
|
|
361
353
|
const isEmpty = value == null || value === '' || (Array.isArray(value) && value.length === 0)
|
|
362
354
|
if (isEmpty) {
|
|
363
|
-
newErrors[key] = field._config.requiredMessage || '
|
|
355
|
+
newErrors[key] = field._config.requiredMessage || $t('form.required')
|
|
364
356
|
isValid = false
|
|
365
357
|
continue
|
|
366
358
|
}
|
|
@@ -12,22 +12,15 @@
|
|
|
12
12
|
import type { FieldBuilder, SchemaDefinition } from './form-flow'
|
|
13
13
|
import { computed, ref, watch } from 'vue'
|
|
14
14
|
|
|
15
|
+
import { useI18n } from '../i18n'
|
|
15
16
|
import { parseQuery } from '../utils/queryFilter'
|
|
16
17
|
|
|
17
18
|
import FormFlow from './FormFlow.vue'
|
|
18
19
|
|
|
19
|
-
export interface Props {
|
|
20
|
-
schema: Record<string, SchemaDefinition>
|
|
21
|
-
modelValue?: Record<string, any>
|
|
22
|
-
currentStep?: string
|
|
23
|
-
components?: Record<string, any>
|
|
24
|
-
/** Whether to validate required fields before allowing next step */
|
|
25
|
-
validateOnNext?: boolean
|
|
26
|
-
}
|
|
27
|
-
|
|
28
20
|
const props = withDefaults(defineProps<Props>(), {
|
|
29
21
|
validateOnNext: true
|
|
30
22
|
})
|
|
23
|
+
|
|
31
24
|
const emit = defineEmits<{
|
|
32
25
|
'update:modelValue': [value: Record<string, any>]
|
|
33
26
|
'update:currentStep': [step: string]
|
|
@@ -36,6 +29,17 @@ const emit = defineEmits<{
|
|
|
36
29
|
'validationError': [errors: Record<string, string>]
|
|
37
30
|
}>()
|
|
38
31
|
|
|
32
|
+
const { $t } = useI18n()
|
|
33
|
+
|
|
34
|
+
export interface Props {
|
|
35
|
+
schema: Record<string, SchemaDefinition>
|
|
36
|
+
modelValue?: Record<string, any>
|
|
37
|
+
currentStep?: string
|
|
38
|
+
components?: Record<string, any>
|
|
39
|
+
/** Whether to validate required fields before allowing next step */
|
|
40
|
+
validateOnNext?: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
39
43
|
// Local form state - initialize with empty object if undefined
|
|
40
44
|
const formData = ref({ ...(props.modelValue || {}) }) as any
|
|
41
45
|
|
|
@@ -366,15 +370,15 @@ defineExpose({
|
|
|
366
370
|
<!-- Navigation Buttons -->
|
|
367
371
|
<div class="step-navigation">
|
|
368
372
|
<button type="button" class="btn-secondary" :disabled="isFirstStep" @click="prevStep">
|
|
369
|
-
|
|
373
|
+
{{ $t('multiStep.previous') }}
|
|
370
374
|
</button>
|
|
371
375
|
|
|
372
376
|
<div class="step-counter">
|
|
373
|
-
|
|
377
|
+
{{ $t('multiStep.step', { current: currentStepIndex + 1, total: visibleSteps.length }) }}
|
|
374
378
|
</div>
|
|
375
379
|
|
|
376
380
|
<button type="button" class="btn-primary" @click="nextStep">
|
|
377
|
-
{{ isLastStep ? '
|
|
381
|
+
{{ isLastStep ? $t('multiStep.complete') : $t('multiStep.next') }}
|
|
378
382
|
</button>
|
|
379
383
|
</div>
|
|
380
384
|
</div>
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { Composer, I18n } from 'vue-i18n'
|
|
2
|
+
import { createI18n as createVueI18n, useI18n as useVueI18n } from 'vue-i18n'
|
|
3
|
+
import en from './locales/en.json'
|
|
4
|
+
import es from './locales/es.json'
|
|
5
|
+
import fr from './locales/fr.json'
|
|
6
|
+
import he from './locales/he.json'
|
|
7
|
+
import it from './locales/it.json'
|
|
8
|
+
import ru from './locales/ru.json'
|
|
9
|
+
|
|
10
|
+
export type MessageSchema = typeof en
|
|
11
|
+
|
|
12
|
+
// Bagelink's built-in locales
|
|
13
|
+
const bagelinkLocales = {
|
|
14
|
+
en,
|
|
15
|
+
he,
|
|
16
|
+
it,
|
|
17
|
+
es,
|
|
18
|
+
fr,
|
|
19
|
+
ru,
|
|
20
|
+
} as const
|
|
21
|
+
|
|
22
|
+
export interface CreateBagelI18nOptions {
|
|
23
|
+
/** Active locale (default: 'en'). Same as vue-i18n's `locale`. */
|
|
24
|
+
locale?: string
|
|
25
|
+
/** Fallback locale (default: 'en'). Same as vue-i18n's `fallbackLocale`. */
|
|
26
|
+
fallbackLocale?: string
|
|
27
|
+
/** User's custom messages merged with bagelink's built-in translations. Same as vue-i18n's `messages`. */
|
|
28
|
+
messages?: Record<string, Record<string, unknown>>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let i18nInstance: I18n | null = null
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Creates an i18n instance with bagelink's translations merged with user's.
|
|
35
|
+
* Options mirror vue-i18n's `createI18n` for consistency.
|
|
36
|
+
*/
|
|
37
|
+
export function createI18n(options: CreateBagelI18nOptions = {}): I18n {
|
|
38
|
+
const {
|
|
39
|
+
locale = 'en',
|
|
40
|
+
fallbackLocale = 'en',
|
|
41
|
+
messages = {},
|
|
42
|
+
} = options
|
|
43
|
+
|
|
44
|
+
// Merge bagelink locales with user messages for all supported locales
|
|
45
|
+
const supportedLocales = Object.keys(bagelinkLocales)
|
|
46
|
+
const mergedMessages: Record<string, Record<string, unknown>> = {}
|
|
47
|
+
|
|
48
|
+
for (const lang of supportedLocales) {
|
|
49
|
+
mergedMessages[lang] = {
|
|
50
|
+
...(bagelinkLocales[lang as keyof typeof bagelinkLocales] || {}),
|
|
51
|
+
...(messages[lang] || {}),
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Also include any user-provided locales not in bagelink's built-ins
|
|
56
|
+
for (const lang of Object.keys(messages)) {
|
|
57
|
+
if (!mergedMessages[lang]) {
|
|
58
|
+
mergedMessages[lang] = messages[lang]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
i18nInstance = createVueI18n({
|
|
63
|
+
locale,
|
|
64
|
+
fallbackLocale,
|
|
65
|
+
messages: mergedMessages as any,
|
|
66
|
+
legacy: false,
|
|
67
|
+
globalInjection: true,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
return i18nInstance
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get the global i18n instance
|
|
75
|
+
*/
|
|
76
|
+
export function getI18n(): I18n {
|
|
77
|
+
if (!i18nInstance) {
|
|
78
|
+
throw new Error('i18n not initialized. Call createI18n() first.')
|
|
79
|
+
}
|
|
80
|
+
return i18nInstance
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface UseI18nReturn {
|
|
84
|
+
$t: Composer['t']
|
|
85
|
+
t: Composer['t']
|
|
86
|
+
locale: Composer['locale']
|
|
87
|
+
availableLocales: Composer['availableLocales']
|
|
88
|
+
fallbackLocale: Composer['fallbackLocale']
|
|
89
|
+
n: Composer['n']
|
|
90
|
+
d: Composer['d']
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* useI18n wrapper that returns bagelink's typed i18n functions
|
|
95
|
+
*/
|
|
96
|
+
export function useI18n(): UseI18nReturn {
|
|
97
|
+
const { t, locale, availableLocales, fallbackLocale, n, d } = useVueI18n()
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
$t: t,
|
|
101
|
+
t,
|
|
102
|
+
locale,
|
|
103
|
+
availableLocales,
|
|
104
|
+
fallbackLocale,
|
|
105
|
+
n,
|
|
106
|
+
d,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Export locale data for direct access if needed
|
|
111
|
+
export { bagelinkLocales }
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* A string that is either a plain string or a translation key prefixed with `$t:`.
|
|
115
|
+
* @example
|
|
116
|
+
* 'Save' // plain string, used as-is
|
|
117
|
+
* '$t:btn.save' // translation key, resolved via i18n
|
|
118
|
+
*/
|
|
119
|
+
export type TranslatableString = string | `$t:${string}`
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Resolves a TranslatableString.
|
|
123
|
+
* If the value starts with `$t:`, it's treated as a translation key.
|
|
124
|
+
* Otherwise the string is returned as-is.
|
|
125
|
+
* Works outside component context (uses the global i18n instance).
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* resolveI18n('Save') // → 'Save'
|
|
129
|
+
* resolveI18n('$t:btn.save') // → 'Salva' (if locale is 'it')
|
|
130
|
+
* resolveI18n(undefined) // → undefined
|
|
131
|
+
*/
|
|
132
|
+
export function resolveI18n(value: TranslatableString | undefined): string | undefined {
|
|
133
|
+
if (value == null) return undefined
|
|
134
|
+
if (!value.startsWith('$t:')) return value
|
|
135
|
+
return (getI18n().global.t as (key: string) => string)(value.slice(3))
|
|
136
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
{
|
|
2
|
+
"modalConfirm": {
|
|
3
|
+
"title": "Confirm",
|
|
4
|
+
"message": "Are you sure you want to continue?",
|
|
5
|
+
"confirm": "Confirm",
|
|
6
|
+
"cancel": "Cancel"
|
|
7
|
+
},
|
|
8
|
+
"upload": {
|
|
9
|
+
"upload": "Upload",
|
|
10
|
+
"uploading": "Uploading {count} file{plural}...",
|
|
11
|
+
"noFile": "No file selected",
|
|
12
|
+
"dropPlaceholder": "Drag and Drop files here or click to upload",
|
|
13
|
+
"delete": "Delete",
|
|
14
|
+
"replace": "Replace",
|
|
15
|
+
"download": "Download"
|
|
16
|
+
},
|
|
17
|
+
"pagination": {
|
|
18
|
+
"of": "of",
|
|
19
|
+
"prev": "Previous",
|
|
20
|
+
"next": "Next"
|
|
21
|
+
},
|
|
22
|
+
"form": {
|
|
23
|
+
"required": "This field is required",
|
|
24
|
+
"invalidEmail": "Please enter a valid email address",
|
|
25
|
+
"invalidUrl": "Please enter a valid URL",
|
|
26
|
+
"minLength": "Must be at least {min} characters",
|
|
27
|
+
"maxLength": "Must be no more than {max} characters",
|
|
28
|
+
"minValue": "Must be at least {min}",
|
|
29
|
+
"maxValue": "Must be no more than {max}",
|
|
30
|
+
"pattern": "Invalid format",
|
|
31
|
+
"passwordStrength": "Password strength"
|
|
32
|
+
},
|
|
33
|
+
"password": {
|
|
34
|
+
"veryWeak": "Very Weak",
|
|
35
|
+
"weak": "Weak",
|
|
36
|
+
"fair": "Fair",
|
|
37
|
+
"good": "Good",
|
|
38
|
+
"strong": "Strong"
|
|
39
|
+
},
|
|
40
|
+
"select": {
|
|
41
|
+
"placeholder": "Select",
|
|
42
|
+
"search": "Search",
|
|
43
|
+
"yes": "Yes",
|
|
44
|
+
"no": "No"
|
|
45
|
+
},
|
|
46
|
+
"fieldArray": {
|
|
47
|
+
"loadDefault": "Load Default {label}"
|
|
48
|
+
},
|
|
49
|
+
"btn": {
|
|
50
|
+
"loading": "Loading..."
|
|
51
|
+
},
|
|
52
|
+
"toast": {
|
|
53
|
+
"success": "Success",
|
|
54
|
+
"error": "Error",
|
|
55
|
+
"warning": "Warning",
|
|
56
|
+
"info": "Information"
|
|
57
|
+
},
|
|
58
|
+
"filter": {
|
|
59
|
+
"filter": "Filter",
|
|
60
|
+
"noActiveFilters": "No active filters",
|
|
61
|
+
"connectors": {
|
|
62
|
+
"and": "And",
|
|
63
|
+
"or": "Or"
|
|
64
|
+
},
|
|
65
|
+
"operators": {
|
|
66
|
+
"eq": "Equals",
|
|
67
|
+
"ne": "Not equals",
|
|
68
|
+
"gt": "Greater than",
|
|
69
|
+
"ge": "Greater or equal",
|
|
70
|
+
"lt": "Less than",
|
|
71
|
+
"le": "Less or equal",
|
|
72
|
+
"co": "Contains",
|
|
73
|
+
"sw": "Starts with",
|
|
74
|
+
"ew": "Ends with",
|
|
75
|
+
"pr": "Present"
|
|
76
|
+
},
|
|
77
|
+
"placeholders": {
|
|
78
|
+
"selectField": "Select field",
|
|
79
|
+
"selectOption": "Select option",
|
|
80
|
+
"selectDate": "Select date",
|
|
81
|
+
"enterValue": "Value"
|
|
82
|
+
},
|
|
83
|
+
"buttons": {
|
|
84
|
+
"addCondition": "Add condition",
|
|
85
|
+
"clearAll": "Clear all"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"multiStep": {
|
|
89
|
+
"previous": "Previous",
|
|
90
|
+
"next": "Next",
|
|
91
|
+
"complete": "Complete",
|
|
92
|
+
"step": "Step {current} of {total}"
|
|
93
|
+
},
|
|
94
|
+
"addressSearch": {
|
|
95
|
+
"placeholder": "Enter address"
|
|
96
|
+
},
|
|
97
|
+
"importData": {
|
|
98
|
+
"close": "Close",
|
|
99
|
+
"cancel": "Cancel",
|
|
100
|
+
"importData": "Import Data",
|
|
101
|
+
"changeFile": "Change File",
|
|
102
|
+
"selectSheet": "Select Sheet",
|
|
103
|
+
"hasHeaders": "Mark this if file has a header row",
|
|
104
|
+
"sourceKeyField": "Source Key Field (from this file)",
|
|
105
|
+
"relatedKeyField": "Related Key Field (from related file)",
|
|
106
|
+
"selectSourceValue": "Select source value",
|
|
107
|
+
"sourceValue": "Source value",
|
|
108
|
+
"selectTargetValue": "Select target value",
|
|
109
|
+
"targetValue": "Target value",
|
|
110
|
+
"selectColumn": "Select column..."
|
|
111
|
+
},
|
|
112
|
+
"timeAgo": {
|
|
113
|
+
"year": "year",
|
|
114
|
+
"month": "month",
|
|
115
|
+
"week": "week",
|
|
116
|
+
"day": "day",
|
|
117
|
+
"hour": "hour",
|
|
118
|
+
"minute": "minute",
|
|
119
|
+
"second": "second",
|
|
120
|
+
"ago": "ago",
|
|
121
|
+
"in": "in",
|
|
122
|
+
"justNow": "Just now"
|
|
123
|
+
},
|
|
124
|
+
"dialogForm": {
|
|
125
|
+
"save": "Save",
|
|
126
|
+
"cancel": "Cancel",
|
|
127
|
+
"delete": "Delete",
|
|
128
|
+
"validationError": "Please fix the validation errors before submitting",
|
|
129
|
+
"errorOccurred": "An error occurred"
|
|
130
|
+
},
|
|
131
|
+
"tel": {
|
|
132
|
+
"phoneNumber": "Phone Number",
|
|
133
|
+
"invalidPhone": "Please enter a valid phone number"
|
|
134
|
+
},
|
|
135
|
+
"email": {
|
|
136
|
+
"invalidEmail": "Please enter a valid email address",
|
|
137
|
+
"invalidDomain": "This email domain appears to be invalid"
|
|
138
|
+
},
|
|
139
|
+
"date": {
|
|
140
|
+
"invalidFormat": "Invalid date format"
|
|
141
|
+
},
|
|
142
|
+
"spreadsheet": {
|
|
143
|
+
"wrapText": "Wrap Text",
|
|
144
|
+
"selectAll": "Select All",
|
|
145
|
+
"clearAll": "Clear All"
|
|
146
|
+
},
|
|
147
|
+
"lightbox": {
|
|
148
|
+
"openFile": "Open File"
|
|
149
|
+
},
|
|
150
|
+
"color": {
|
|
151
|
+
"placeholder": "Enter color"
|
|
152
|
+
}
|
|
153
|
+
}
|