Form Translation (#616)
* Form Translation * Support for other languages * Support locale for datepicker * Apply translation on select input * Apply translation on select input * Improve translation --------- Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
@@ -71,6 +71,7 @@
|
||||
:max-date="maxDate"
|
||||
:is-dark="props.isDark"
|
||||
color="form-color"
|
||||
:locale="props.locale"
|
||||
@update:model-value="updateModelValue"
|
||||
/>
|
||||
<DatePicker
|
||||
@@ -84,6 +85,7 @@
|
||||
:max-date="maxDate"
|
||||
:is-dark="props.isDark"
|
||||
color="form-color"
|
||||
:locale="props.locale"
|
||||
@update:model-value="updateModelValue"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
>
|
||||
<Loader class="mx-auto h-6 w-6" />
|
||||
<p class="mt-2 text-center text-sm text-gray-500">
|
||||
Uploading your file...
|
||||
{{ $t('forms.fileInput.uploadingFile') }}
|
||||
</p>
|
||||
</div>
|
||||
<template v-else>
|
||||
@@ -95,10 +95,10 @@
|
||||
</div>
|
||||
|
||||
<p class="mt-2 text-sm text-gray-500 font-medium select-none">
|
||||
Click to choose {{ multiple ? 'file(s)' : 'a file' }} or drag here
|
||||
{{ $t('forms.fileInput.chooseFiles', { n: multiple ? 1 : 0 }) }}
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-gray-400 dark:text-gray-600 select-none">
|
||||
Size limit: {{ mbLimit }}MB per file
|
||||
{{ $t('forms.fileInput.sizeLimit', mbLimit) }}
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
'!text-gray-500 !cursor-not-allowed'
|
||||
]"
|
||||
>
|
||||
No options available.
|
||||
{{ $t('forms.select.noOptionAvailable') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
>
|
||||
<Loader class="mx-auto h-6 w-6" />
|
||||
<p class="mt-2 text-center text-sm text-gray-500">
|
||||
Uploading your file...
|
||||
{{ $t('forms.fileInput.uploadingFile') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
:class="theme.default.help"
|
||||
href="#"
|
||||
@click.prevent="openFileUpload"
|
||||
>Upload file instead</a>
|
||||
>{{ $t('forms.signatureInput.uploadFileInstead') }}</a>
|
||||
</small>
|
||||
|
||||
<small :class="theme.default.help">
|
||||
@@ -84,7 +84,7 @@
|
||||
:class="theme.default.help"
|
||||
href="#"
|
||||
@click.prevent="clear"
|
||||
>Clear</a>
|
||||
>{{ $t('forms.signatureInput.clear') }}</a>
|
||||
</small>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -49,17 +49,16 @@
|
||||
class="w-6 h-6"
|
||||
/>
|
||||
<p class="text-center font-bold">
|
||||
Allow Camera Permission
|
||||
{{ $t('forms.cameraUpload.allowCameraPermission') }}
|
||||
</p>
|
||||
<p class="text-xs">
|
||||
You need to allow camera permission before you can take pictures. Go to
|
||||
browser settings to enable camera permission on this page.
|
||||
{{ $t('forms.cameraUpload.allowCameraPermissionDescription') }}
|
||||
</p>
|
||||
<UButton
|
||||
color="white"
|
||||
@click.stop="cancelCamera"
|
||||
>
|
||||
Got it!
|
||||
{{ $t('forms.cameraUpload.gotIt') }}
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
@@ -81,16 +80,16 @@
|
||||
class="w-6 h-6"
|
||||
/>
|
||||
<p class="text-center font-bold">
|
||||
Camera Device Error
|
||||
{{ $t('forms.cameraUpload.cameraDeviceError') }}
|
||||
</p>
|
||||
<p class="text-xs">
|
||||
An unknown error occurred when trying to start Webcam device.
|
||||
{{ $t('forms.cameraUpload.cameraDeviceErrorDescription') }}
|
||||
</p>
|
||||
<UButton
|
||||
color="white"
|
||||
@click.stop="cancelCamera"
|
||||
>
|
||||
Go back
|
||||
{{ $t('forms.cameraUpload.goBack') }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
v-model="searchTerm"
|
||||
type="text"
|
||||
class="flex-grow ltr:pl-3 ltr:pr-7 rtl:!pr-3 rtl:pl-7 py-2 w-full focus:outline-none dark:text-white"
|
||||
placeholder="Search"
|
||||
:placeholder="allowCreation ? $t('forms.select.searchOrTypeToCreateNew') : $t('forms.select.search')"
|
||||
>
|
||||
<div
|
||||
v-if="!searchTerm"
|
||||
@@ -179,7 +179,7 @@
|
||||
v-else-if="!loading && !(allowCreation && searchTerm)"
|
||||
class="w-full text-gray-500 text-center py-2"
|
||||
>
|
||||
{{ (allowCreation ? 'Type something to add an option' : 'No option available') }}.
|
||||
{{ (allowCreation ? $t('forms.select.typeSomethingToAddAnOption') : $t('forms.select.noOptionAvailable')) }}.
|
||||
</p>
|
||||
<div
|
||||
v-if="allowCreation && searchTerm"
|
||||
@@ -192,7 +192,7 @@
|
||||
class="text-gray-900 select-none relative py-2 cursor-pointer group hover:bg-gray-100 dark:hover:bg-gray-900 rounded focus:outline-none"
|
||||
@click="createOption(searchTerm)"
|
||||
>
|
||||
Create <span class="px-2 bg-gray-100 border border-gray-300 rounded group-hover-text-black">{{
|
||||
{{ $t('forms.select.create') }} <span class="px-2 bg-gray-100 border border-gray-300 rounded group-hover-text-black">{{
|
||||
searchTerm
|
||||
}}</span>
|
||||
</li>
|
||||
|
||||
3
client/components/forms/useFormInput.js
vendored
3
client/components/forms/useFormInput.js
vendored
@@ -28,7 +28,8 @@ export const inputProps = {
|
||||
help: {type: String, default: null},
|
||||
helpPosition: {type: String, default: "below_input"},
|
||||
color: {type: String, default: "#3B82F6"},
|
||||
wrapperClass: {type: String, default: ""},
|
||||
wrapperClass: { type: String, default: "" },
|
||||
locale: { type: String, default: "en" },
|
||||
}
|
||||
|
||||
export function useFormInput(props, context, options = {}) {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
<div v-if="isPublicFormPage && form.is_password_protected">
|
||||
<p class="form-description mb-4 text-gray-700 dark:text-gray-300 px-2">
|
||||
This form is protected by a password.
|
||||
{{ $t('forms.password_protected') }}
|
||||
</p>
|
||||
<div class="form-group flex flex-wrap w-full">
|
||||
<div class="relative mb-3 w-full px-2">
|
||||
@@ -47,7 +47,7 @@
|
||||
class="my-4"
|
||||
@click="passwordEntered"
|
||||
>
|
||||
Submit
|
||||
{{ $t('forms.submit') }}
|
||||
</open-form-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,7 +139,7 @@
|
||||
class="text-gray-400 hover:text-gray-500 dark:text-gray-600 dark:hover:text-gray-500 cursor-pointer hover:underline text-xs"
|
||||
target="_blank"
|
||||
>
|
||||
Powered by <span class="font-semibold">OpnForm</span>
|
||||
{{ $t('forms.powered_by') }} <span class="font-semibold">{{ $t('app.name') }}</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -186,7 +186,7 @@
|
||||
href="https://opnform.com/?utm_source=form&utm_content=create_form_free"
|
||||
class="text-nt-blue hover:underline"
|
||||
>
|
||||
Create your form for free with OpnForm
|
||||
{{ $t('forms.create_form_free') }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -202,7 +202,6 @@
|
||||
<script>
|
||||
import OpenForm from './OpenForm.vue'
|
||||
import OpenFormButton from './OpenFormButton.vue'
|
||||
import FormTimer from './FormTimer.vue'
|
||||
import FormCleanings from '../../pages/forms/show/FormCleanings.vue'
|
||||
import VTransition from '~/components/global/transitions/VTransition.vue'
|
||||
import {pendingSubmission} from "~/composables/forms/pendingSubmission.js"
|
||||
@@ -211,7 +210,7 @@ import ThemeBuilder from "~/lib/forms/themes/ThemeBuilder.js"
|
||||
import FirstSubmissionModal from '~/components/open/forms/components/FirstSubmissionModal.vue'
|
||||
|
||||
export default {
|
||||
components: { VTransition, OpenFormButton, OpenForm, FormCleanings, FormTimer, FirstSubmissionModal },
|
||||
components: { VTransition, OpenFormButton, OpenForm, FormCleanings, FirstSubmissionModal },
|
||||
|
||||
props: {
|
||||
form: { type: Object, required: true },
|
||||
@@ -225,8 +224,11 @@ export default {
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const { setLocale } = useI18n()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
return {
|
||||
setLocale,
|
||||
authStore,
|
||||
authenticated: computed(() => authStore.check),
|
||||
isIframe: useIsIframe(),
|
||||
@@ -274,6 +276,17 @@ export default {
|
||||
return this.authenticated && this.form && this.form.creator_id === this.authStore.user.id
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'form.language': {
|
||||
handler(newLanguage) {
|
||||
this.setLocale(newLanguage)
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.setLocale('en')
|
||||
},
|
||||
|
||||
methods: {
|
||||
submitForm (form, onFailure) {
|
||||
@@ -346,7 +359,7 @@ export default {
|
||||
if (this.passwordForm.password !== '' && this.passwordForm.password !== null) {
|
||||
this.$emit('password-entered', this.passwordForm.password)
|
||||
} else {
|
||||
this.addPasswordError('The Password field is required.')
|
||||
this.addPasswordError(this.$t('forms.password_required'))
|
||||
}
|
||||
},
|
||||
addPasswordError (msg) {
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
{{ currentFieldsPageBreak.next_btn_text }}
|
||||
</open-form-button>
|
||||
<div v-if="!currentFieldsPageBreak && !isLastPage">
|
||||
Something is wrong with this form structure. If you're the form owner please contact us.
|
||||
{{ $t('forms.wrong_form_structure') }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -329,7 +329,8 @@ export default {
|
||||
theme: this.theme,
|
||||
maxCharLimit: (field.max_char_limit) ? parseInt(field.max_char_limit) : null,
|
||||
showCharLimit: field.show_char_limit || false,
|
||||
isDark: this.darkMode
|
||||
isDark: this.darkMode,
|
||||
locale: (this.form?.language) ? this.form.language : 'en'
|
||||
}
|
||||
|
||||
if (field.type === 'matrix') {
|
||||
|
||||
@@ -74,6 +74,14 @@
|
||||
label="Uppercase Input Labels"
|
||||
/>
|
||||
|
||||
<select-input
|
||||
name="language"
|
||||
class="mt-4"
|
||||
:options="availableLocales"
|
||||
:form="form"
|
||||
label="Form Language"
|
||||
/>
|
||||
|
||||
<EditorSectionHeader
|
||||
icon="heroicons:rectangle-stack-16-solid"
|
||||
title="Layout & Sizing"
|
||||
@@ -215,6 +223,7 @@ const form = storeToRefs(workingFormStore).content
|
||||
const isMounted = ref(false)
|
||||
const confetti = useConfetti()
|
||||
const showGoogleFontPicker = ref(false)
|
||||
const { $i18n } = useNuxtApp()
|
||||
|
||||
const user = computed(() => authStore.user)
|
||||
const workspace = computed(() => workspacesStore.getCurrent)
|
||||
@@ -225,6 +234,10 @@ const isPro = computed(() => {
|
||||
return workspace.value.is_pro
|
||||
})
|
||||
|
||||
const availableLocales = computed(() => {
|
||||
return $i18n.locales?.value.map(locale => ({ name: locale.name, value: locale.code })) ?? []
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
isMounted.value = true
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user