feat(i18n): add full-stack internationalization support
Add languages and entity_translations tables to database schema Implement localized queries and translation management in backend Integrate frontend i18n and add translation UI components
This commit is contained in:
76
frontend/src/components/TranslationFields.vue
Normal file
76
frontend/src/components/TranslationFields.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { Language, TranslationField, TranslationMap } from '../services/api';
|
||||
|
||||
const props = defineProps<{
|
||||
idPrefix: string;
|
||||
field: TranslationField;
|
||||
label: string;
|
||||
baseValue: string;
|
||||
translations: TranslationMap;
|
||||
languages: Language[];
|
||||
required?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:baseValue': [value: string];
|
||||
'update:translations': [value: TranslationMap];
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const visibleLanguages = computed(() => props.languages.filter((language) => language.enabled));
|
||||
const defaultLanguage = computed(() => visibleLanguages.value.find((language) => language.isDefault) ?? visibleLanguages.value[0]);
|
||||
|
||||
function fieldValue(language: Language): string {
|
||||
if (language.code === defaultLanguage.value?.code) {
|
||||
return props.baseValue;
|
||||
}
|
||||
|
||||
return props.translations[language.code]?.[props.field] ?? '';
|
||||
}
|
||||
|
||||
function updateField(language: Language, value: string) {
|
||||
if (language.code === defaultLanguage.value?.code) {
|
||||
emit('update:baseValue', value);
|
||||
return;
|
||||
}
|
||||
|
||||
const nextTranslations: TranslationMap = { ...props.translations };
|
||||
const nextFields = { ...(nextTranslations[language.code] ?? {}) };
|
||||
|
||||
if (value.trim() === '') {
|
||||
delete nextFields[props.field];
|
||||
} else {
|
||||
nextFields[props.field] = value;
|
||||
}
|
||||
|
||||
if (Object.keys(nextFields).length) {
|
||||
nextTranslations[language.code] = nextFields;
|
||||
} else {
|
||||
delete nextTranslations[language.code];
|
||||
}
|
||||
|
||||
emit('update:translations', nextTranslations);
|
||||
}
|
||||
|
||||
function inputValue(event: Event): string {
|
||||
return event.target instanceof HTMLInputElement ? event.target.value : '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="translation-fields">
|
||||
<div v-for="language in visibleLanguages" :key="language.code" class="field">
|
||||
<label :for="`${idPrefix}-${language.code}`">
|
||||
{{ t('common.fieldForLanguage', { field: label, language: language.name }) }}
|
||||
</label>
|
||||
<input
|
||||
:id="`${idPrefix}-${language.code}`"
|
||||
:value="fieldValue(language)"
|
||||
:required="required && language.code === defaultLanguage?.code"
|
||||
@input="updateField(language, inputValue($event))"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user