From 82523fa1a622e44bbd74abd5f31f4650f9bae9b5 Mon Sep 17 00:00:00 2001 From: Chirag Chhatrala Date: Thu, 14 Nov 2024 20:36:45 +0530 Subject: [PATCH 1/3] Form Translation --- api/app/Http/Requests/AnswerFormRequest.php | 6 + api/app/Http/Requests/UserFormRequest.php | 1 + api/app/Http/Resources/FormResource.php | 1 + api/app/Models/Forms/Form.php | 3 + api/config/app.php | 1 + api/database/factories/FormFactory.php | 1 + ..._08_133330_add_language_to_forms_table.php | 27 + api/resources/lang/fr/validation.php | 117 ++++ client/components/forms/FileInput.vue | 6 +- client/components/forms/SignatureInput.vue | 6 +- .../forms/components/CameraUpload.vue | 13 +- .../open/forms/OpenCompleteForm.vue | 16 +- .../form-components/FormCustomization.vue | 13 + client/composables/forms/initForm.js | 1 + client/i18n/lang/en.json | 21 + client/i18n/lang/fr.json | 21 + client/nuxt.config.ts | 13 +- client/package-lock.json | 570 +++++++++++++++++- client/package.json | 1 + client/pages/forms/[slug]/index.vue | 4 + 20 files changed, 823 insertions(+), 19 deletions(-) create mode 100644 api/database/migrations/2024_11_08_133330_add_language_to_forms_table.php create mode 100644 api/resources/lang/fr/validation.php create mode 100644 client/i18n/lang/en.json create mode 100644 client/i18n/lang/fr.json diff --git a/api/app/Http/Requests/AnswerFormRequest.php b/api/app/Http/Requests/AnswerFormRequest.php index 8bfc10ee9..75eec8217 100644 --- a/api/app/Http/Requests/AnswerFormRequest.php +++ b/api/app/Http/Requests/AnswerFormRequest.php @@ -245,6 +245,12 @@ private function getSelectPropertyOptions($property): array protected function prepareForValidation() { + // Set locale based on form language + if ($this->form?->language && in_array($this->form->language, Form::LANGUAGES)) { + app()->setLocale($this->form->language); + } + + $receivedData = $this->toArray(); $mergeData = []; $countryCodeMapper = json_decode(file_get_contents(resource_path('data/country_code_mapper.json')), true); diff --git a/api/app/Http/Requests/UserFormRequest.php b/api/app/Http/Requests/UserFormRequest.php index b79bd8eba..f8042ad06 100644 --- a/api/app/Http/Requests/UserFormRequest.php +++ b/api/app/Http/Requests/UserFormRequest.php @@ -29,6 +29,7 @@ public function rules() 'visibility' => ['required', Rule::in(Form::VISIBILITY)], // Customization + 'language' => ['required', Rule::in(Form::LANGUAGES)], 'font_family' => 'string|nullable', 'theme' => ['required', Rule::in(Form::THEMES)], 'width' => ['required', Rule::in(Form::WIDTHS)], diff --git a/api/app/Http/Resources/FormResource.php b/api/app/Http/Resources/FormResource.php index 6686fcbd9..d3f4ac457 100644 --- a/api/app/Http/Resources/FormResource.php +++ b/api/app/Http/Resources/FormResource.php @@ -70,6 +70,7 @@ private function getProtectedForm() 'dark_mode' => $this->dark_mode, 'transparent_background' => $this->transparent_background, 'color' => $this->color, + 'language' => $this->language, 'theme' => $this->theme, 'is_password_protected' => true, 'has_password' => $this->has_password, diff --git a/api/app/Models/Forms/Form.php b/api/app/Models/Forms/Form.php index d6d50a35f..4c97977d8 100644 --- a/api/app/Models/Forms/Form.php +++ b/api/app/Models/Forms/Form.php @@ -41,6 +41,8 @@ class Form extends Model implements CachableAttributes public const VISIBILITY = ['public', 'draft', 'closed']; + public const LANGUAGES = ['en', 'fr']; + protected $fillable = [ 'workspace_id', 'creator_id', @@ -52,6 +54,7 @@ class Form extends Model implements CachableAttributes 'visibility', // Customization + 'language', 'font_family', 'custom_domain', 'size', diff --git a/api/config/app.php b/api/config/app.php index 51907366d..41918f79d 100644 --- a/api/config/app.php +++ b/api/config/app.php @@ -94,6 +94,7 @@ 'locales' => [ 'en' => 'EN', + 'fr' => 'FR', ], /* diff --git a/api/database/factories/FormFactory.php b/api/database/factories/FormFactory.php index a8d91d9d9..91d505e0d 100644 --- a/api/database/factories/FormFactory.php +++ b/api/database/factories/FormFactory.php @@ -50,6 +50,7 @@ public function definition() 'title' => $this->faker->text(30), 'description' => $this->faker->randomHtml(1), 'visibility' => 'public', + 'language' => 'en', 'theme' => $this->faker->randomElement(Form::THEMES), 'size' => $this->faker->randomElement(Form::SIZES), 'border_radius' => $this->faker->randomElement(Form::BORDER_RADIUS), diff --git a/api/database/migrations/2024_11_08_133330_add_language_to_forms_table.php b/api/database/migrations/2024_11_08_133330_add_language_to_forms_table.php new file mode 100644 index 000000000..7a26af408 --- /dev/null +++ b/api/database/migrations/2024_11_08_133330_add_language_to_forms_table.php @@ -0,0 +1,27 @@ +string('language')->default('en'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('forms', function (Blueprint $table) { + $table->dropColumn('language'); + }); + } +}; diff --git a/api/resources/lang/fr/validation.php b/api/resources/lang/fr/validation.php new file mode 100644 index 000000000..242ad3d59 --- /dev/null +++ b/api/resources/lang/fr/validation.php @@ -0,0 +1,117 @@ + 'Le champ :attribute doit être accepté.', + 'active_url' => 'Le champ :attribute n\'est pas une URL valide.', + 'after' => 'Le champ :attribute doit être une date postérieure au :date.', + 'after_or_equal' => 'Le champ :attribute doit être une date postérieure ou égale au :date.', + 'alpha' => 'Le champ :attribute doit contenir uniquement des lettres.', + 'alpha_dash' => 'Le champ :attribute doit contenir uniquement des lettres, des chiffres, des tirets et des underscores.', + 'alpha_num' => 'Le champ :attribute doit contenir uniquement des lettres et des chiffres.', + 'array' => 'Le champ :attribute doit être un tableau.', + 'before' => 'Le champ :attribute doit être une date antérieure au :date.', + 'before_or_equal' => 'Le champ :attribute doit être une date antérieure ou égale au :date.', + 'between' => [ + 'numeric' => 'La valeur de :attribute doit être comprise entre :min et :max.', + 'file' => 'La taille du fichier de :attribute doit être comprise entre :min et :max kilo-octets.', + 'string' => 'Le texte :attribute doit contenir entre :min et :max caractères.', + 'array' => 'Le tableau :attribute doit contenir entre :min et :max éléments.', + ], + 'boolean' => 'Le champ :attribute doit être vrai ou faux.', + 'confirmed' => 'Le champ de confirmation :attribute ne correspond pas.', + 'date' => 'Le champ :attribute n\'est pas une date valide.', + 'date_equals' => 'Le champ :attribute doit être une date égale à :date.', + 'date_format' => 'Le champ :attribute ne correspond pas au format :format.', + 'different' => 'Les champs :attribute et :other doivent être différents.', + 'digits' => 'Le champ :attribute doit contenir :digits chiffres.', + 'digits_between' => 'Le champ :attribute doit contenir entre :min et :max chiffres.', + 'dimensions' => 'Les dimensions de l\'image :attribute ne sont pas valides.', + 'distinct' => 'Le champ :attribute a une valeur en double.', + 'email' => 'Le champ :attribute doit être une adresse e-mail valide.', + 'ends_with' => 'Le champ :attribute doit se terminer par une des valeurs suivantes : :values.', + 'exists' => 'Le champ :attribute sélectionné est invalide.', + 'file' => 'Le champ :attribute doit être un fichier.', + 'filled' => 'Le champ :attribute doit avoir une valeur.', + 'gt' => [ + 'numeric' => 'La valeur de :attribute doit être supérieure à :value.', + 'file' => 'La taille du fichier de :attribute doit être supérieure à :value kilo-octets.', + 'string' => 'Le texte :attribute doit contenir plus de :value caractères.', + 'array' => 'Le tableau :attribute doit contenir plus de :value éléments.', + ], + 'gte' => [ + 'numeric' => 'La valeur de :attribute doit être supérieure ou égale à :value.', + 'file' => 'La taille du fichier de :attribute doit être supérieure ou égale à :value kilo-octets.', + 'string' => 'Le texte :attribute doit contenir au moins :value caractères.', + 'array' => 'Le tableau :attribute doit contenir au moins :value éléments.', + ], + 'image' => 'Le champ :attribute doit être une image.', + 'in' => 'Le champ :attribute est invalide.', + 'in_array' => 'Le champ :attribute n\'existe pas dans :other.', + 'integer' => 'Le champ :attribute doit être un entier.', + 'ip' => 'Le champ :attribute doit être une adresse IP valide.', + 'ipv4' => 'Le champ :attribute doit être une adresse IPv4 valide.', + 'ipv6' => 'Le champ :attribute doit être une adresse IPv6 valide.', + 'json' => 'Le champ :attribute doit être un document JSON valide.', + 'lt' => [ + 'numeric' => 'La valeur de :attribute doit être inférieure à :value.', + 'file' => 'La taille du fichier de :attribute doit être inférieure à :value kilo-octets.', + 'string' => 'Le texte :attribute doit contenir moins de :value caractères.', + 'array' => 'Le tableau :attribute doit contenir moins de :value éléments.', + ], + 'lte' => [ + 'numeric' => 'La valeur de :attribute doit être inférieure ou égale à :value.', + 'file' => 'La taille du fichier de :attribute doit être inférieure ou égale à :value kilo-octets.', + 'string' => 'Le texte :attribute doit contenir au plus :value caractères.', + 'array' => 'Le tableau :attribute doit contenir au plus :value éléments.', + ], + 'max' => [ + 'numeric' => 'La valeur de :attribute ne peut être supérieure à :max.', + 'file' => 'La taille du fichier de :attribute ne peut pas dépasser :max kilo-octets.', + 'string' => 'Le texte de :attribute ne peut contenir plus de :max caractères.', + 'array' => 'Le tableau :attribute ne peut contenir plus de :max éléments.', + ], + 'mimes' => 'Le champ :attribute doit être un fichier de type : :values.', + 'mimetypes' => 'Le champ :attribute doit être un fichier de type : :values.', + 'min' => [ + 'numeric' => 'La valeur de :attribute doit être supérieure ou égale à :min.', + 'file' => 'La taille du fichier de :attribute doit être supérieure à :min kilo-octets.', + 'string' => 'Le texte :attribute doit contenir au moins :min caractères.', + 'array' => 'Le tableau :attribute doit contenir au moins :min éléments.', + ], + 'multiple_of' => 'La valeur de :attribute doit être un multiple de :value', + 'not_in' => 'Le champ :attribute sélectionné n\'est pas valide.', + 'not_regex' => 'Le format du champ :attribute n\'est pas valide.', + 'numeric' => 'Le champ :attribute doit contenir un nombre.', + 'password' => 'Le mot de passe est incorrect.', + 'present' => 'Le champ :attribute doit être présent.', + 'regex' => 'Le format du champ :attribute est invalide.', + 'required' => 'Le champ :attribute est obligatoire.', + 'required_if' => 'Le champ :attribute est obligatoire quand :other est :value.', + 'required_unless' => 'Le champ :attribute est obligatoire sauf si :other est :values.', + 'required_with' => 'Le champ :attribute est obligatoire quand :values est présent.', + 'required_with_all' => 'Le champ :attribute est obligatoire quand :values sont présents.', + 'required_without' => 'Le champ :attribute est obligatoire quand :values n\'est pas présent.', + 'required_without_all' => 'Le champ :attribute est requis quand aucun de :values n\'est présent.', + 'same' => 'Les champs :attribute et :other doivent être identiques.', + 'size' => [ + 'numeric' => 'La valeur de :attribute doit être :size.', + 'file' => 'La taille du fichier de :attribute doit être de :size kilo-octets.', + 'string' => 'Le texte de :attribute doit contenir :size caractères.', + 'array' => 'Le tableau :attribute doit contenir :size éléments.', + ], + 'starts_with' => 'Le champ :attribute doit commencer avec une des valeurs suivantes : :values.', + 'string' => 'Le champ :attribute doit être une chaîne de caractères.', + 'timezone' => 'Le champ :attribute doit être un fuseau horaire valide.', + 'unique' => 'La valeur du champ :attribute est déjà utilisée.', + 'uploaded' => 'Le fichier du champ :attribute n\'a pu être téléversé.', + 'url' => 'Le format de l\'URL de :attribute n\'est pas valide.', + 'uuid' => 'Le champ :attribute doit être un UUID valide.', + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'message-personnalisé', + ], + ], + + 'attributes' => [], +]; diff --git a/client/components/forms/FileInput.vue b/client/components/forms/FileInput.vue index d7e6316da..e97d809b5 100644 --- a/client/components/forms/FileInput.vue +++ b/client/components/forms/FileInput.vue @@ -50,7 +50,7 @@ >

- Uploading your file... + {{ $t('forms.fileInput.uploadingFile') }}

diff --git a/client/components/forms/SignatureInput.vue b/client/components/forms/SignatureInput.vue index 22b3911c4..ca73f3793 100644 --- a/client/components/forms/SignatureInput.vue +++ b/client/components/forms/SignatureInput.vue @@ -25,7 +25,7 @@ >

- Uploading your file... + {{ $t('forms.fileInput.uploadingFile') }}

@@ -76,7 +76,7 @@ :class="theme.default.help" href="#" @click.prevent="openFileUpload" - >Upload file instead + >{{ $t('forms.signatureInput.uploadFileInstead') }} @@ -84,7 +84,7 @@ :class="theme.default.help" href="#" @click.prevent="clear" - >Clear + >{{ $t('forms.signatureInput.clear') }} diff --git a/client/components/forms/components/CameraUpload.vue b/client/components/forms/components/CameraUpload.vue index 9426e4f90..39184cb19 100644 --- a/client/components/forms/components/CameraUpload.vue +++ b/client/components/forms/components/CameraUpload.vue @@ -49,17 +49,16 @@ class="w-6 h-6" />

- Allow Camera Permission + {{ $t('forms.cameraUpload.allowCameraPermission') }}

- 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') }}

- Got it! + {{ $t('forms.cameraUpload.gotIt') }} @@ -81,16 +80,16 @@ class="w-6 h-6" />

- Camera Device Error + {{ $t('forms.cameraUpload.cameraDeviceError') }}

- An unknown error occurred when trying to start Webcam device. + {{ $t('forms.cameraUpload.cameraDeviceErrorDescription') }}

- Go back + {{ $t('forms.cameraUpload.goBack') }} diff --git a/client/components/open/forms/OpenCompleteForm.vue b/client/components/open/forms/OpenCompleteForm.vue index cde381d17..c9e844d0c 100644 --- a/client/components/open/forms/OpenCompleteForm.vue +++ b/client/components/open/forms/OpenCompleteForm.vue @@ -147,7 +147,7 @@ key="submitted" class="px-2" > - authStore.check), isIframe: useIsIframe(), @@ -273,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) { diff --git a/client/components/open/forms/components/form-components/FormCustomization.vue b/client/components/open/forms/components/form-components/FormCustomization.vue index 83fd92101..298fa444e 100644 --- a/client/components/open/forms/components/form-components/FormCustomization.vue +++ b/client/components/open/forms/components/form-components/FormCustomization.vue @@ -74,6 +74,14 @@ label="Uppercase Input Labels" /> + + authStore.user) const workspace = computed(() => workspacesStore.getCurrent) @@ -216,6 +225,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 }) diff --git a/client/composables/forms/initForm.js b/client/composables/forms/initForm.js index cd2e1b015..ad0353f2f 100644 --- a/client/composables/forms/initForm.js +++ b/client/composables/forms/initForm.js @@ -10,6 +10,7 @@ export const initForm = (defaultValue = {}, withDefaultProperties = false) => { properties: withDefaultProperties ? getDefaultProperties() : [], // Customization + language: 'en', font_family: null, theme: "default", width: "centered", diff --git a/client/i18n/lang/en.json b/client/i18n/lang/en.json new file mode 100644 index 000000000..523eeb086 --- /dev/null +++ b/client/i18n/lang/en.json @@ -0,0 +1,21 @@ +{ + "forms": { + "fileInput": { + "chooseFiles": "Click to choose file(s) or drag here | Click to choose a file or drag here", + "sizeLimit": "Size limit: {count}MB per file", + "uploadingFile": "Uploading your file..." + }, + "cameraUpload": { + "allowCameraPermission": "Allow Camera Permission", + "allowCameraPermissionDescription": "You need to allow camera permission before you can take pictures. Go to browser settings to enable camera permission on this page.", + "gotIt": "Got it!", + "cameraDeviceError": "Camera Device Error", + "cameraDeviceErrorDescription": "An unknown error occurred when trying to start Webcam device.", + "goBack": "Go back" + }, + "signatureInput": { + "uploadFileInstead": "Upload file instead", + "clear": "Clear" + } + } +} \ No newline at end of file diff --git a/client/i18n/lang/fr.json b/client/i18n/lang/fr.json new file mode 100644 index 000000000..f064ee08b --- /dev/null +++ b/client/i18n/lang/fr.json @@ -0,0 +1,21 @@ +{ + "forms": { + "fileInput": { + "chooseFiles": "Cliquez pour choisir un/des fichier(s) ou déposez-le(s) ici | Cliquez pour choisir un fichier ou déposez-le ici", + "sizeLimit": "Taille limite: {count}MB par fichier", + "uploadingFile": "Envoi de votre fichier en cours..." + }, + "cameraUpload": { + "allowCameraPermission": "Autoriser l'accès à la caméra", + "allowCameraPermissionDescription": "Vous devez autoriser l'accès à la caméra avant de pouvoir prendre des photos. Allez dans les paramètres du navigateur pour activer l'accès à la caméra sur cette page.", + "gotIt": "J'ai compris !", + "cameraDeviceError": "Erreur de caméra", + "cameraDeviceErrorDescription": "Une erreur inconnue est survenue lors de la tentative de démarrage du périphérique webcam.", + "goBack": "Retour" + }, + "signatureInput": { + "uploadFileInstead": "Uploader un fichier à la place", + "clear": "Effacer" + } + } +} \ No newline at end of file diff --git a/client/nuxt.config.ts b/client/nuxt.config.ts index 76d537a57..f23a8cd79 100644 --- a/client/nuxt.config.ts +++ b/client/nuxt.config.ts @@ -15,12 +15,23 @@ export default defineNuxtConfig({ '@nuxtjs/sitemap', '@nuxt/ui', 'nuxt-utm', + '@nuxtjs/i18n', '@nuxt/icon', ...process.env.NUXT_PUBLIC_GTM_CODE ? ['@zadigetvoltaire/nuxt-gtm'] : [], -], + ], build: { transpile: ["vue-notion", "query-builder-vue-3", "vue-signature-pad"], }, + i18n: { + locales: [ + { code: 'en', name: 'English', iso: 'en-US', file: 'en.json' }, + { code: 'fr', name: 'French', iso: 'fr-FR', file: 'fr.json' }, + ], + defaultLocale: 'en', + lazy: true, + langDir: 'lang/', + strategy: 'no_prefix' + }, experimental: { inlineRouteRules: true }, diff --git a/client/package-lock.json b/client/package-lock.json index 1f22c3878..9633801e1 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -54,6 +54,7 @@ "@nuxt/devtools": "~1.0.0", "@nuxt/eslint-config": "^0.2.0", "@nuxt/icon": "^1.6.1", + "@nuxtjs/i18n": "^9.0.0", "@nuxtjs/sitemap": "^6.1.3", "@zadigetvoltaire/nuxt-gtm": "^0.0.13", "autoprefixer": "^10.4.20", @@ -1571,6 +1572,349 @@ "vue": ">=3" } }, + "node_modules/@intlify/bundle-utils": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-9.0.0.tgz", + "integrity": "sha512-19dunbgM4wuCvi2xSai2PKhXkcKGjlbJhNWm9BCQWkUYcPmXwzptNWOE0O7OSrhNlEDxwpkHsJzZ/vLbCkpElw==", + "dev": true, + "dependencies": { + "@intlify/message-compiler": "next", + "@intlify/shared": "next", + "acorn": "^8.8.2", + "escodegen": "^2.1.0", + "estree-walker": "^2.0.2", + "jsonc-eslint-parser": "^2.3.0", + "mlly": "^1.2.0", + "source-map-js": "^1.0.1", + "yaml-eslint-parser": "^1.2.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependenciesMeta": { + "petite-vue-i18n": { + "optional": true + }, + "vue-i18n": { + "optional": true + } + } + }, + "node_modules/@intlify/bundle-utils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@intlify/core": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@intlify/core/-/core-10.0.4.tgz", + "integrity": "sha512-YVb0Hk0vgulAhC/uq5CMUXLMcmDL+24FWcjRk2RMn2EF8ZrM3ZV9+8dGOKextntN1/RtjK3RvoHicF48vMyYkA==", + "dev": true, + "dependencies": { + "@intlify/core-base": "10.0.4", + "@intlify/shared": "10.0.4" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/core-base": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-10.0.4.tgz", + "integrity": "sha512-GG428DkrrWCMhxRMRQZjuS7zmSUzarYcaHJqG9VB8dXAxw4iQDoKVQ7ChJRB6ZtsCsX3Jse1PEUlHrJiyQrOTg==", + "dev": true, + "dependencies": { + "@intlify/message-compiler": "10.0.4", + "@intlify/shared": "10.0.4" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/h3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@intlify/h3/-/h3-0.6.0.tgz", + "integrity": "sha512-tWBm92pYLT+T2H5I2Uwz0dnylX1uRKuS6/n9CV4eTW43r/iAN2q07b0sY2cvgT61KYDetomY1pVRkzA2Rftv5g==", + "dev": true, + "dependencies": { + "@intlify/core": "^10.0.3", + "@intlify/utils": "^0.13.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-10.0.4.tgz", + "integrity": "sha512-AFbhEo10DP095/45EauinQJ5hJ3rJUmuuqltGguvc3WsvezZN+g8qNHLGWKu60FHQVizMrQY7VJ+zVlBXlQQkQ==", + "dev": true, + "dependencies": { + "@intlify/shared": "10.0.4", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-10.0.4.tgz", + "integrity": "sha512-ukFn0I01HsSgr3VYhYcvkTCLS7rGa0gw4A4AMpcy/A9xx/zRJy7PS2BElMXLwUazVFMAr5zuiTk3MQeoeGXaJg==", + "dev": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/unplugin-vue-i18n": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-5.2.0.tgz", + "integrity": "sha512-pmRiPY2Nj9mmSrixT69aO45XxGUr5fDBy/IIw4ajLlDTJm5TSmQKA5YNdsH0uxVDCPWy5tlQrF18hkDwI7UJvg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@intlify/bundle-utils": "^9.0.0-beta.0", + "@intlify/shared": "next", + "@intlify/vue-i18n-extensions": "^7.0.0", + "@rollup/pluginutils": "^5.1.0", + "@typescript-eslint/scope-manager": "^7.13.0", + "@typescript-eslint/typescript-estree": "^7.13.0", + "debug": "^4.3.3", + "fast-glob": "^3.2.12", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "pathe": "^1.0.0", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2", + "unplugin": "^1.1.0", + "vue": "^3.4" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "petite-vue-i18n": "*", + "vue": "^3.2.25", + "vue-i18n": "*" + }, + "peerDependenciesMeta": { + "petite-vue-i18n": { + "optional": true + }, + "vue-i18n": { + "optional": true + } + } + }, + "node_modules/@intlify/unplugin-vue-i18n/node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@intlify/unplugin-vue-i18n/node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@intlify/unplugin-vue-i18n/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@intlify/unplugin-vue-i18n/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@intlify/unplugin-vue-i18n/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@intlify/unplugin-vue-i18n/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@intlify/unplugin-vue-i18n/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@intlify/unplugin-vue-i18n/node_modules/unplugin": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.15.0.tgz", + "integrity": "sha512-jTPIs63W+DUEDW207ztbaoO7cQ4p5aVaB823LSlxpsFEU3Mykwxf3ZGC/wzxFJeZlASZYgVrWeo7LgOrqJZ8RA==", + "dev": true, + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "webpack-sources": "^3" + }, + "peerDependenciesMeta": { + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/@intlify/unplugin-vue-i18n/node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true + }, + "node_modules/@intlify/utils": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@intlify/utils/-/utils-0.13.0.tgz", + "integrity": "sha512-8i3uRdAxCGzuHwfmHcVjeLQBtysQB2aXl/ojoagDut5/gY5lvWCQ2+cnl2TiqE/fXj/D8EhWG/SLKA7qz4a3QA==", + "dev": true, + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/vue-i18n-extensions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@intlify/vue-i18n-extensions/-/vue-i18n-extensions-7.0.0.tgz", + "integrity": "sha512-MtvfJnb4aklpCU5Q/dkWkBT/vGsp3qERiPIwtTq5lX4PCLHtUprAJZp8wQj5ZcwDaFCU7+yVMjYbeXpIf927cA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.24.6", + "@intlify/shared": "^10.0.0", + "@vue/compiler-dom": "^3.2.45", + "vue-i18n": "^10.0.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@intlify/shared": "^9.0.0 || ^10.0.0", + "@vue/compiler-dom": "^3.0.0", + "vue": "^3.0.0", + "vue-i18n": "^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "@intlify/shared": { + "optional": true + }, + "@vue/compiler-dom": { + "optional": true + }, + "vue": { + "optional": true + }, + "vue-i18n": { + "optional": true + } + } + }, "node_modules/@ioredis/commands": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", @@ -1860,6 +2204,19 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, + "node_modules/@miyaneee/rollup-plugin-json5": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@miyaneee/rollup-plugin-json5/-/rollup-plugin-json5-1.2.0.tgz", + "integrity": "sha512-JjTIaXZp9WzhUHpElrqPnl1AzBi/rvRs065F71+aTmlqvTMVkdbjZ8vfFl4nRlgJy+TPBw69ZK4pwFdmOAt4aA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.0", + "json5": "^2.2.3" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, "node_modules/@netlify/functions": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-2.8.2.tgz", @@ -2746,6 +3103,76 @@ "semver": "^7.6.3" } }, + "node_modules/@nuxtjs/i18n": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@nuxtjs/i18n/-/i18n-9.0.0.tgz", + "integrity": "sha512-olAVD7ZPNVxWpOgj5VJrtWaqkVfKSDQmJENfed7t6TwDjVggPHPHMpmw4rbudDsr9cdCIBR30hvUEXm2m7s2BA==", + "dev": true, + "dependencies": { + "@intlify/h3": "^0.6.0", + "@intlify/shared": "^10.0.3", + "@intlify/unplugin-vue-i18n": "^5.2.0", + "@intlify/utils": "^0.13.0", + "@miyaneee/rollup-plugin-json5": "^1.2.0", + "@nuxt/kit": "^3.13.2", + "@rollup/plugin-yaml": "^4.1.2", + "@vue/compiler-sfc": "^3.5.5", + "debug": "^4.3.5", + "defu": "^6.1.2", + "estree-walker": "^3.0.3", + "is-https": "^4.0.0", + "knitwork": "^1.1.0", + "magic-string": "^0.30.10", + "mlly": "^1.7.1", + "pathe": "^1.1.1", + "scule": "^1.1.1", + "sucrase": "^3.35.0", + "ufo": "^1.3.1", + "unplugin": "^1.10.1", + "unplugin-vue-router": "^0.10.8", + "vue-i18n": "^10.0.3", + "vue-router": "^4.4.5" + }, + "engines": { + "node": "^14.16.0 || >=16.11.0" + } + }, + "node_modules/@nuxtjs/i18n/node_modules/magic-string": { + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/@nuxtjs/i18n/node_modules/unplugin": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.15.0.tgz", + "integrity": "sha512-jTPIs63W+DUEDW207ztbaoO7cQ4p5aVaB823LSlxpsFEU3Mykwxf3ZGC/wzxFJeZlASZYgVrWeo7LgOrqJZ8RA==", + "dev": true, + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "webpack-sources": "^3" + }, + "peerDependenciesMeta": { + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/@nuxtjs/i18n/node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true + }, "node_modules/@nuxtjs/sitemap": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/@nuxtjs/sitemap/-/sitemap-6.1.3.tgz", @@ -3385,6 +3812,28 @@ } } }, + "node_modules/@rollup/plugin-yaml": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-yaml/-/plugin-yaml-4.1.2.tgz", + "integrity": "sha512-RpupciIeZMUqhgFE97ba0s98mOFS7CWzN3EJNhJkqSv9XLlWYtwVdtE6cDw6ASOF/sZVFS7kRJXftaqM2Vakdw==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "js-yaml": "^4.1.0", + "tosource": "^2.0.0-alpha.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/pluginutils": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.2.tgz", @@ -5237,10 +5686,9 @@ } }, "node_modules/acorn": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", - "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", - "license": "MIT", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "bin": { "acorn": "bin/acorn" }, @@ -7264,6 +7712,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/eslint": { "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", @@ -7585,6 +8064,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -8804,6 +9296,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-https": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-https/-/is-https-4.0.0.tgz", + "integrity": "sha512-FeMLiqf8E5g6SdiVJsPcNZX8k4h2fBs1wp5Bb6uaNxn58ufK1axBqQZdmAQsqh0t9BuwFObybrdVJh6MKyPlyg==", + "dev": true + }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -9097,6 +9595,24 @@ "node": ">=6" } }, + "node_modules/jsonc-eslint-parser": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.0.tgz", + "integrity": "sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==", + "dev": true, + "dependencies": { + "acorn": "^8.5.0", + "eslint-visitor-keys": "^3.0.0", + "espree": "^9.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -15166,6 +15682,15 @@ "node": ">=0.6" } }, + "node_modules/tosource": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/tosource/-/tosource-2.0.0-alpha.3.tgz", + "integrity": "sha512-KAB2lrSS48y91MzFPFuDg4hLbvDiyTjOVgaK7Erw+5AmZXNq4sFRVn8r6yxSLuNs15PaokrDRpS61ERY9uZOug==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", @@ -16726,6 +17251,26 @@ "eslint": ">=6.0.0" } }, + "node_modules/vue-i18n": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-10.0.4.tgz", + "integrity": "sha512-1xkzVxqBLk2ZFOmeI+B5r1J7aD/WtNJ4j9k2mcFcQo5BnOmHBmD7z4/oZohh96AAaRZ4Q7mNQvxc9h+aT+Md3w==", + "dev": true, + "dependencies": { + "@intlify/core-base": "10.0.4", + "@intlify/shared": "10.0.4", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/vue-json-pretty": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/vue-json-pretty/-/vue-json-pretty-2.4.0.tgz", @@ -16988,6 +17533,23 @@ "node": ">= 14" } }, + "node_modules/yaml-eslint-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.2.3.tgz", + "integrity": "sha512-4wZWvE398hCP7O8n3nXKu/vdq1HcH01ixYlCREaJL5NUMwQ0g3MaGFUBNSlmBtKmhbtVG/Cm6lyYmSVTEVil8A==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.0.0", + "lodash": "^4.17.21", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, "node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", diff --git a/client/package.json b/client/package.json index f94a47d72..8f965f981 100644 --- a/client/package.json +++ b/client/package.json @@ -16,6 +16,7 @@ "@nuxt/devtools": "~1.0.0", "@nuxt/eslint-config": "^0.2.0", "@nuxt/icon": "^1.6.1", + "@nuxtjs/i18n": "^9.0.0", "@nuxtjs/sitemap": "^6.1.3", "@zadigetvoltaire/nuxt-gtm": "^0.0.13", "autoprefixer": "^10.4.20", diff --git a/client/pages/forms/[slug]/index.vue b/client/pages/forms/[slug]/index.vue index c793f1e58..14d8abfbf 100644 --- a/client/pages/forms/[slug]/index.vue +++ b/client/pages/forms/[slug]/index.vue @@ -234,7 +234,11 @@ useOpnSeoMeta({ return (form.value && form.value?.can_be_indexed) ? null : 'noindex, nofollow' } }) + useHead({ + htmlAttrs: { + lang: (form.value?.language) ? form.value.language : 'en' + }, titleTemplate: (titleChunk) => { if (pageMeta.value.page_title) { // Disable template if custom SEO title From 6417bbd69aefec08cf240f90c2948d95afe28ff2 Mon Sep 17 00:00:00 2001 From: Chirag Chhatrala Date: Wed, 20 Nov 2024 15:46:30 +0530 Subject: [PATCH 2/3] Support for other languages --- api/app/Models/Forms/Form.php | 2 +- api/config/app.php | 5 ++ api/resources/lang/ar/validation.php | 117 +++++++++++++++++++++++++++ api/resources/lang/hi/validation.php | 117 +++++++++++++++++++++++++++ api/resources/lang/ja/validation.php | 117 +++++++++++++++++++++++++++ api/resources/lang/zh/validation.php | 117 +++++++++++++++++++++++++++ client/i18n/lang/ar.json | 21 +++++ client/i18n/lang/es.json | 21 +++++ client/i18n/lang/hi.json | 21 +++++ client/i18n/lang/ja.json | 21 +++++ client/i18n/lang/pt.json | 21 +++++ client/i18n/lang/zh.json | 21 +++++ client/nuxt.config.ts | 5 ++ 13 files changed, 605 insertions(+), 1 deletion(-) create mode 100644 api/resources/lang/ar/validation.php create mode 100644 api/resources/lang/hi/validation.php create mode 100644 api/resources/lang/ja/validation.php create mode 100644 api/resources/lang/zh/validation.php create mode 100644 client/i18n/lang/ar.json create mode 100644 client/i18n/lang/es.json create mode 100644 client/i18n/lang/hi.json create mode 100644 client/i18n/lang/ja.json create mode 100644 client/i18n/lang/pt.json create mode 100644 client/i18n/lang/zh.json diff --git a/api/app/Models/Forms/Form.php b/api/app/Models/Forms/Form.php index 4c97977d8..28e22639c 100644 --- a/api/app/Models/Forms/Form.php +++ b/api/app/Models/Forms/Form.php @@ -41,7 +41,7 @@ class Form extends Model implements CachableAttributes public const VISIBILITY = ['public', 'draft', 'closed']; - public const LANGUAGES = ['en', 'fr']; + public const LANGUAGES = ['en', 'fr', 'hi', 'es', 'ar', 'zh', 'ja']; protected $fillable = [ 'workspace_id', diff --git a/api/config/app.php b/api/config/app.php index 41918f79d..72b7b02c8 100644 --- a/api/config/app.php +++ b/api/config/app.php @@ -95,6 +95,11 @@ 'locales' => [ 'en' => 'EN', 'fr' => 'FR', + 'hi' => 'HI', + 'es' => 'ES', + 'ar' => 'AR', + 'zh' => 'ZH', + 'ja' => 'JA', ], /* diff --git a/api/resources/lang/ar/validation.php b/api/resources/lang/ar/validation.php new file mode 100644 index 000000000..da972c907 --- /dev/null +++ b/api/resources/lang/ar/validation.php @@ -0,0 +1,117 @@ + 'يجب قبول :attribute.', + 'active_url' => ':attribute لا يُمثل رابطًا صحيحًا.', + 'after' => 'يجب على :attribute أن يكون تاريخًا لاحقًا للتاريخ :date.', + 'after_or_equal' => ':attribute يجب أن يكون تاريخاً لاحقاً أو مطابقاً للتاريخ :date.', + 'alpha' => 'يجب أن لا يحتوي :attribute سوى على حروف.', + 'alpha_dash' => 'يجب أن لا يحتوي :attribute سوى على حروف، أرقام ومطّات.', + 'alpha_num' => 'يجب أن يحتوي :attribute على حروفٍ وأرقامٍ فقط.', + 'array' => 'يجب أن يكون :attribute ًمصفوفة.', + 'before' => 'يجب على :attribute أن يكون تاريخًا سابقًا للتاريخ :date.', + 'before_or_equal' => ':attribute يجب أن يكون تاريخا سابقا أو مطابقا للتاريخ :date.', + 'between' => [ + 'numeric' => 'يجب أن تكون قيمة :attribute بين :min و :max.', + 'file' => 'يجب أن يكون حجم الملف :attribute بين :min و :max كيلوبايت.', + 'string' => 'يجب أن يكون عدد حروف النّص :attribute بين :min و :max.', + 'array' => 'يجب أن يحتوي :attribute على عدد من العناصر بين :min و :max.', + ], + 'boolean' => 'يجب أن تكون قيمة :attribute إما true أو false.', + 'confirmed' => 'حقل التأكيد غير مُطابق للحقل :attribute.', + 'date' => ':attribute ليس تاريخًا صحيحًا.', + 'date_equals' => 'يجب أن يكون :attribute مطابقاً للتاريخ :date.', + 'date_format' => 'لا يتوافق :attribute مع الشكل :format.', + 'different' => 'يجب أن يكون الحقلان :attribute و :other مُختلفين.', + 'digits' => 'يجب أن يحتوي :attribute على :digits رقمًا/أرقام.', + 'digits_between' => 'يجب أن يحتوي :attribute بين :min و :max رقمًا/أرقام.', + 'dimensions' => 'الـ :attribute يحتوي على أبعاد صورة غير صالحة.', + 'distinct' => 'للحقل :attribute قيمة مُكرّرة.', + 'email' => 'يجب أن يكون :attribute عنوان بريد إلكتروني صحيح البُنية.', + 'ends_with' => 'يجب أن ينتهي :attribute بأحد القيم التالية: :values', + 'exists' => 'القيمة المحددة :attribute غير موجودة.', + 'file' => 'الـ :attribute يجب أن يكون ملفا.', + 'filled' => ':attribute إجباري.', + 'gt' => [ + 'numeric' => 'يجب أن تكون قيمة :attribute أكبر من :value.', + 'file' => 'يجب أن يكون حجم الملف :attribute أكبر من :value كيلوبايت.', + 'string' => 'يجب أن يكون طول النّص :attribute أكثر من :value حروفٍ/حرفًا.', + 'array' => 'يجب أن يحتوي :attribute على أكثر من :value عناصر/عنصر.', + ], + 'gte' => [ + 'numeric' => 'يجب أن تكون قيمة :attribute مساوية أو أكبر من :value.', + 'file' => 'يجب أن يكون حجم الملف :attribute على الأقل :value كيلوبايت.', + 'string' => 'يجب أن يكون طول النص :attribute على الأقل :value حروفٍ/حرفًا.', + 'array' => 'يجب أن يحتوي :attribute على الأقل على :value عُنصرًا/عناصر.', + ], + 'image' => 'يجب أن يكون :attribute صورةً.', + 'in' => ':attribute غير موجود.', + 'in_array' => ':attribute غير موجود في :other.', + 'integer' => 'يجب أن يكون :attribute عددًا صحيحًا.', + 'ip' => 'يجب أن يكون :attribute عنوان IP صحيحًا.', + 'ipv4' => 'يجب أن يكون :attribute عنوان IPv4 صحيحًا.', + 'ipv6' => 'يجب أن يكون :attribute عنوان IPv6 صحيحًا.', + 'json' => 'يجب أن يكون :attribute نصًا من نوع JSON.', + 'lt' => [ + 'numeric' => 'يجب أن تكون قيمة :attribute أصغر من :value.', + 'file' => 'يجب أن يكون حجم الملف :attribute أصغر من :value كيلوبايت.', + 'string' => 'يجب أن يكون طول النّص :attribute أقل من :value حروفٍ/حرفًا.', + 'array' => 'يجب أن يحتوي :attribute على أقل من :value عناصر/عنصر.', + ], + 'lte' => [ + 'numeric' => 'يجب أن تكون قيمة :attribute مساوية أو أصغر من :value.', + 'file' => 'يجب أن لا يتجاوز حجم الملف :attribute :value كيلوبايت.', + 'string' => 'يجب أن لا يتجاوز طول النّص :attribute :value حروفٍ/حرفًا.', + 'array' => 'يجب أن لا يحتوي :attribute على أكثر من :value عناصر/عنصر.', + ], + 'max' => [ + 'numeric' => 'يجب أن تكون قيمة :attribute مساوية أو أصغر من :max.', + 'file' => 'يجب أن لا يتجاوز حجم الملف :attribute :max كيلوبايت.', + 'string' => 'يجب أن لا يتجاوز طول النّص :attribute :max حروفٍ/حرفًا.', + 'array' => 'يجب أن لا يحتوي :attribute على أكثر من :max عناصر/عنصر.', + ], + 'mimes' => 'يجب أن يكون ملفًا من نوع : :values.', + 'mimetypes' => 'يجب أن يكون ملفًا من نوع : :values.', + 'min' => [ + 'numeric' => 'يجب أن تكون قيمة :attribute مساوية أو أكبر من :min.', + 'file' => 'يجب أن يكون حجم الملف :attribute على الأقل :min كيلوبايت.', + 'string' => 'يجب أن يكون طول النص :attribute على الأقل :min حروفٍ/حرفًا.', + 'array' => 'يجب أن يحتوي :attribute على الأقل على :min عُنصرًا/عناصر.', + ], + 'multiple_of' => ':attribute يجب أن يكون من مضاعفات :value', + 'not_in' => 'العنصر :attribute غير صحيح.', + 'not_regex' => 'صيغة :attribute غير صحيحة.', + 'numeric' => 'يجب على :attribute أن يكون رقمًا.', + 'password' => 'كلمة المرور غير صحيحة.', + 'present' => 'يجب تقديم :attribute.', + 'regex' => 'صيغة :attribute غير صحيحة.', + 'required' => ':attribute مطلوب.', + 'required_if' => ':attribute مطلوب في حال ما إذا كان :other يساوي :value.', + 'required_unless' => ':attribute مطلوب في حال ما لم يكن :other يساوي :values.', + 'required_with' => ':attribute مطلوب إذا توفّر :values.', + 'required_with_all' => ':attribute مطلوب إذا توفّر :values.', + 'required_without' => ':attribute مطلوب إذا لم يتوفّر :values.', + 'required_without_all' => ':attribute مطلوب إذا لم يتوفّر :values.', + 'same' => 'يجب أن يتطابق :attribute مع :other.', + 'size' => [ + 'numeric' => 'يجب أن تكون قيمة :attribute مساوية لـ :size.', + 'file' => 'يجب أن يكون حجم الملف :attribute :size كيلوبايت.', + 'string' => 'يجب أن يحتوي النص :attribute على :size حروفٍ/حرفًا بالضبط.', + 'array' => 'يجب أن يحتوي :attribute على :size عنصرٍ/عناصر بالضبط.', + ], + 'starts_with' => 'يجب أن يبدأ :attribute بأحد القيم التالية: :values', + 'string' => 'يجب أن يكون :attribute نصًا.', + 'timezone' => 'يجب أن يكون :attribute نطاقًا زمنيًا صحيحًا.', + 'unique' => 'قيمة :attribute مُستخدمة من قبل.', + 'uploaded' => 'فشل في تحميل الـ :attribute.', + 'url' => 'صيغة الرابط :attribute غير صحيحة.', + 'uuid' => ':attribute يجب أن يكون بصيغة UUID سليمة.', + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'رسالة-مخصصة', + ], + ], + + 'attributes' => [], +]; diff --git a/api/resources/lang/hi/validation.php b/api/resources/lang/hi/validation.php new file mode 100644 index 000000000..d2960e973 --- /dev/null +++ b/api/resources/lang/hi/validation.php @@ -0,0 +1,117 @@ + ':attribute को स्वीकार किया जाना चाहिए।', + 'active_url' => ':attribute एक मान्य URL नहीं है।', + 'after' => ':attribute, :date के बाद की एक तारीख होनी चाहिए।', + 'after_or_equal' => ':attribute, :date के बाद या उसके बराबर की तारीख होनी चाहिए।', + 'alpha' => ':attribute में केवल अक्षर हो सकते हैं।', + 'alpha_dash' => ':attribute में केवल अक्षर, संख्या, डैश और अंडरस्कोर हो सकते हैं।', + 'alpha_num' => ':attribute में केवल अक्षर और संख्याएं हो सकती हैं।', + 'array' => ':attribute एक एरे होना चाहिए।', + 'before' => ':attribute, :date से पहले की एक तारीख होनी चाहिए।', + 'before_or_equal' => ':attribute, :date से पहले या उसके बराबर की तारीख होनी चाहिए।', + 'between' => [ + 'numeric' => ':attribute, :min और :max के बीच होना चाहिए।', + 'file' => ':attribute, :min और :max किलोबाइट के बीच होना चाहिए।', + 'string' => ':attribute, :min और :max वर्णों के बीच होना चाहिए।', + 'array' => ':attribute, :min और :max आइटमों के बीच होना चाहिए।', + ], + 'boolean' => ':attribute फील्ड सही या गलत होना चाहिए।', + 'confirmed' => ':attribute पुष्टिकरण मेल नहीं खा रहा है।', + 'date' => ':attribute एक मान्य तारीख नहीं है।', + 'date_equals' => ':attribute, :date के बराबर की तारीख होनी चाहिए।', + 'date_format' => ':attribute फॉर्मेट :format से मेल नहीं खा रहा है।', + 'different' => ':attribute और :other अलग होना चाहिए।', + 'digits' => ':attribute, :digits अंक होना चाहिए।', + 'digits_between' => ':attribute, :min और :max अंकों के बीच होना चाहिए।', + 'dimensions' => ':attribute की अमान्य छवि आयाम हैं।', + 'distinct' => ':attribute फील्ड का एक डुप्लिकेट मान है।', + 'email' => ':attribute एक मान्य ईमेल पता होना चाहिए।', + 'ends_with' => ':attribute निम्न में से किसी एक के साथ समाप्त होना चाहिए: :values', + 'exists' => 'चयनित :attribute अमान्य है।', + 'file' => ':attribute एक फ़ाइल होनी चाहिए।', + 'filled' => ':attribute फील्ड आवश्यक है।', + 'gt' => [ + 'numeric' => ':attribute, :value से बड़ा होना चाहिए।', + 'file' => ':attribute, :value किलोबाइट से बड़ा होना चाहिए।', + 'string' => ':attribute, :value वर्णों से बड़ा होना चाहिए।', + 'array' => ':attribute, :value आइटमों से अधिक होना चाहिए।', + ], + 'gte' => [ + 'numeric' => ':attribute, :value से बड़ा या बराबर होना चाहिए।', + 'file' => ':attribute, :value किलोबाइट से बड़ा या बराबर होना चाहिए।', + 'string' => ':attribute, :value वर्णों से बड़ा या बराबर होना चाहिए।', + 'array' => ':attribute में :value आइटम या अधिक होने चाहिए।', + ], + 'image' => ':attribute एक छवि होनी चाहिए।', + 'in' => 'चयनित :attribute अमान्य है।', + 'in_array' => ':attribute फील्ड, :other में मौजूद नहीं है।', + 'integer' => ':attribute एक पूर्णांक होना चाहिए।', + 'ip' => ':attribute एक मान्य IP पता होना चाहिए।', + 'ipv4' => ':attribute एक मान्य IPv4 पता होना चाहिए।', + 'ipv6' => ':attribute एक मान्य IPv6 पता होना चाहिए।', + 'json' => ':attribute एक मान्य JSON स्ट्रिंग होना चाहिए।', + 'lt' => [ + 'numeric' => ':attribute, :value से छोटा होना चाहिए।', + 'file' => ':attribute, :value किलोबाइट से छोटा होना चाहिए।', + 'string' => ':attribute, :value वर्णों से छोटा होना चाहिए।', + 'array' => ':attribute, :value आइटमों से कम होना चाहिए।', + ], + 'lte' => [ + 'numeric' => ':attribute, :value से छोटा या बराबर होना चाहिए।', + 'file' => ':attribute, :value किलोबाइट से छोटा या बराबर होना चाहिए।', + 'string' => ':attribute, :value वर्णों से छोटा या बराबर होना चाहिए।', + 'array' => ':attribute में :value आइटम से अधिक नहीं होना चाहिए।', + ], + 'max' => [ + 'numeric' => ':attribute, :max से बड़ा नहीं हो सकता है।', + 'file' => ':attribute, :max किलोबाइट से बड़ा नहीं हो सकता है।', + 'string' => ':attribute, :max वर्णों से बड़ा नहीं हो सकता है।', + 'array' => ':attribute में :max से अधिक आइटम नहीं हो सकते हैं।', + ], + 'mimes' => ':attribute एक प्रकार की फ़ाइल: :values होनी चाहिए।', + 'mimetypes' => ':attribute एक प्रकार की फ़ाइल: :values होनी चाहिए।', + 'min' => [ + 'numeric' => ':attribute कम से कम :min होना चाहिए।', + 'file' => ':attribute कम से कम :min किलोबाइट होना चाहिए।', + 'string' => ':attribute कम से कम :min वर्ण होना चाहिए।', + 'array' => ':attribute में कम से कम :min आइटम होने चाहिए।', + ], + 'multiple_of' => ':attribute, :value का गुणज होना चाहिए।', + 'not_in' => 'चयनित :attribute अमान्य है।', + 'not_regex' => ':attribute प्रारूप अमान्य है।', + 'numeric' => ':attribute एक संख्या होनी चाहिए।', + 'password' => 'पासवर्ड गलत है।', + 'present' => ':attribute फील्ड मौजूद होना चाहिए।', + 'regex' => ':attribute प्रारूप अमान्य है।', + 'required' => ':attribute फील्ड आवश्यक है।', + 'required_if' => ':attribute फील्ड आवश्यक है जब :other :value है।', + 'required_unless' => ':attribute फील्ड आवश्यक है जब तक :other, :values में नहीं है।', + 'required_with' => ':attribute फील्ड आवश्यक है जब :values मौजूद है।', + 'required_with_all' => ':attribute फील्ड आवश्यक है जब :values मौजूद हैं।', + 'required_without' => ':attribute फील्ड आवश्यक है जब :values मौजूद नहीं है।', + 'required_without_all' => ':attribute फील्ड आवश्यक है जब :values में से कोई भी मौजूद नहीं है।', + 'same' => ':attribute और :other मेल खाने चाहिए।', + 'size' => [ + 'numeric' => ':attribute, :size होना चाहिए।', + 'file' => ':attribute, :size किलोबाइट होना चाहिए।', + 'string' => ':attribute, :size वर्ण होना चाहिए।', + 'array' => ':attribute में :size आइटम होने चाहिए।', + ], + 'starts_with' => ':attribute निम्न में से किसी एक से शुरू होना चाहिए: :values', + 'string' => ':attribute एक स्ट्रिंग होनी चाहिए।', + 'timezone' => ':attribute एक मान्य क्षेत्र होना चाहिए।', + 'unique' => ':attribute पहले से ही लिया गया है।', + 'uploaded' => ':attribute अपलोड करने में विफल रहा।', + 'url' => ':attribute प्रारूप अमान्य है।', + 'uuid' => ':attribute एक मान्य UUID होना चाहिए।', + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'कस्टम-संदेश', + ], + ], + + 'attributes' => [], +]; diff --git a/api/resources/lang/ja/validation.php b/api/resources/lang/ja/validation.php new file mode 100644 index 000000000..fc2f8df63 --- /dev/null +++ b/api/resources/lang/ja/validation.php @@ -0,0 +1,117 @@ + ':attributeを承認してください。', + 'active_url' => ':attributeは、有効なURLではありません。', + 'after' => ':attributeには、:dateより後の日付を指定してください。', + 'after_or_equal' => ':attributeには、:date以降の日付を指定してください。', + 'alpha' => ':attributeには、アルファベッドのみ使用できます。', + 'alpha_dash' => ':attributeには、英数字、ハイフン、アンダースコアのみ使用できます。', + 'alpha_num' => ':attributeには、英数字のみ使用できます。', + 'array' => ':attributeには、配列を指定してください。', + 'before' => ':attributeには、:dateより前の日付を指定してください。', + 'before_or_equal' => ':attributeには、:date以前の日付を指定してください。', + 'between' => [ + 'numeric' => ':attributeには、:minから:maxまでの数字を指定してください。', + 'file' => ':attributeには、:min KBから:max KBまでのサイズのファイルを指定してください。', + 'string' => ':attributeは、:min文字から:max文字にしてください。', + 'array' => ':attributeの項目は、:min個から:max個にしてください。', + ], + 'boolean' => ':attributeには、trueかfalseを指定してください。', + 'confirmed' => ':attributeと確認用の値が一致しません。', + 'date' => ':attributeは、正しい日付ではありません。', + 'date_equals' => ':attributeは:dateと同じ日付でなければなりません。', + 'date_format' => ':attributeの形式は、:formatと合いません。', + 'different' => ':attributeと:otherには、異なった内容を指定してください。', + 'digits' => ':attributeは、:digits桁にしてください。', + 'digits_between' => ':attributeは、:min桁から:max桁にしてください。', + 'dimensions' => ':attributeの画像サイズが無効です。', + 'distinct' => ':attributeの値が重複しています。', + 'email' => ':attributeには、有効なメールアドレスを指定してください。', + 'ends_with' => ':attributeは、次のいずれかで終わる必要があります。: :values', + 'exists' => '選択された:attributeは、有効ではありません。', + 'file' => ':attributeには、ファイルを指定してください。', + 'filled' => ':attributeには、値を指定してください。', + 'gt' => [ + 'numeric' => ':attributeは、:valueより大きくなければなりません。', + 'file' => ':attributeは、:value KBより大きくなければなりません。', + 'string' => ':attributeは、:value文字より長くなければなりません。', + 'array' => ':attributeの項目数は、:value個より多くなければなりません。', + ], + 'gte' => [ + 'numeric' => ':attributeは、:value以上でなければなりません。', + 'file' => ':attributeは、:value KB以上でなければなりません。', + 'string' => ':attributeは、:value文字以上でなければなりません。', + 'array' => ':attributeの項目数は、:value個以上でなければなりません。', + ], + 'image' => ':attributeには、画像を指定してください。', + 'in' => '選択された:attributeは、有効ではありません。', + 'in_array' => ':attributeが:otherに存在しません。', + 'integer' => ':attributeには、整数を指定してください。', + 'ip' => ':attributeには、有効なIPアドレスを指定してください。', + 'ipv4' => ':attributeはIPv4アドレスを指定してください。', + 'ipv6' => ':attributeはIPv6アドレスを指定してください。', + 'json' => ':attributeには、有効なJSON文字列を指定してください。', + 'lt' => [ + 'numeric' => ':attributeは、:valueより小さくなければなりません。', + 'file' => ':attributeは、:value KBより小さくなければなりません。', + 'string' => ':attributeは、:value文字より短くなければなりません。', + 'array' => ':attributeの項目数は、:value個より少なくなければなりません。', + ], + 'lte' => [ + 'numeric' => ':attributeは、:value以下でなければなりません。', + 'file' => ':attributeは、:value KB以下でなければなりません。', + 'string' => ':attributeは、:value文字以下でなければなりません。', + 'array' => ':attributeの項目数は、:value個以下でなければなりません。', + ], + 'max' => [ + 'numeric' => ':attributeには、:max以下の数字を指定してください。', + 'file' => ':attributeには、:max KB以下のファイルを指定してください。', + 'string' => ':attributeは、:max文字以下にしてください。', + 'array' => ':attributeの項目は、:max個以下にしてください。', + ], + 'mimes' => ':attributeには、:valuesタイプのファイルを指定してください。', + 'mimetypes' => ':attributeには、:valuesタイプのファイルを指定してください。', + 'min' => [ + 'numeric' => ':attributeには、:min以上の数字を指定してください。', + 'file' => ':attributeには、:min KB以上のファイルを指定してください。', + 'string' => ':attributeは、:min文字以上にしてください。', + 'array' => ':attributeの項目は、:min個以上にしてください。', + ], + 'multiple_of' => ':attributeは:valueの倍数でなければなりません。', + 'not_in' => '選択された:attributeは、有効ではありません。', + 'not_regex' => ':attributeの形式が無効です。', + 'numeric' => ':attributeには、数字を指定してください。', + 'password' => 'パスワードが正しくありません。', + 'present' => ':attributeが存在している必要があります。', + 'regex' => ':attributeには、有効な正規表現を指定してください。', + 'required' => ':attributeは、必ず指定してください。', + 'required_if' => ':otherが:valueの場合、:attributeを指定してください。', + 'required_unless' => ':otherが:values以外の場合、:attributeを指定してください。', + 'required_with' => ':valuesが指定されている場合、:attributeも指定してください。', + 'required_with_all' => ':valuesが全て指定されている場合、:attributeも指定してください。', + 'required_without' => ':valuesが指定されていない場合、:attributeを指定してください。', + 'required_without_all' => ':valuesが全て指定されていない場合、:attributeを指定してください。', + 'same' => ':attributeと:otherが一致しません。', + 'size' => [ + 'numeric' => ':attributeには、:sizeを指定してください。', + 'file' => ':attributeには、:size KBのファイルを指定してください。', + 'string' => ':attributeは、:size文字にしてください。', + 'array' => ':attributeの項目は、:size個にしてください。', + ], + 'starts_with' => ':attributeは、次のいずれかで始まる必要があります。: :values', + 'string' => ':attributeには、文字を指定してください。', + 'timezone' => ':attributeには、有効なタイムゾーンを指定してください。', + 'unique' => '指定の:attributeは既に使用されています。', + 'uploaded' => ':attributeのアップロードに失敗しました。', + 'url' => ':attributeは、有効なURL形式で指定してください。', + 'uuid' => ':attributeは、有効なUUIDでなければなりません。', + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'カスタムメッセージ', + ], + ], + + 'attributes' => [], +]; diff --git a/api/resources/lang/zh/validation.php b/api/resources/lang/zh/validation.php new file mode 100644 index 000000000..e7bf82dd4 --- /dev/null +++ b/api/resources/lang/zh/validation.php @@ -0,0 +1,117 @@ + ':attribute 必须被接受。', + 'active_url' => ':attribute 不是一个有效的网址。', + 'after' => ':attribute 必须是 :date 之后的日期。', + 'after_or_equal' => ':attribute 必须是 :date 之后或相同的日期。', + 'alpha' => ':attribute 只能由字母组成。', + 'alpha_dash' => ':attribute 只能由字母、数字、短划线和下划线组成。', + 'alpha_num' => ':attribute 只能由字母和数字组成。', + 'array' => ':attribute 必须是一个数组。', + 'before' => ':attribute 必须是 :date 之前的日期。', + 'before_or_equal' => ':attribute 必须是 :date 之前或相同的日期。', + 'between' => [ + 'numeric' => ':attribute 必须介于 :min - :max 之间。', + 'file' => ':attribute 必须介于 :min - :max KB 之间。', + 'string' => ':attribute 必须介于 :min - :max 个字符之间。', + 'array' => ':attribute 必须只有 :min - :max 个单元。', + ], + 'boolean' => ':attribute 必须为布尔值。', + 'confirmed' => ':attribute 两次输入不一致。', + 'date' => ':attribute 不是一个有效的日期。', + 'date_equals' => ':attribute 必须等于 :date。', + 'date_format' => ':attribute 的格式必须为 :format。', + 'different' => ':attribute 和 :other 必须不同。', + 'digits' => ':attribute 必须是 :digits 位数字。', + 'digits_between' => ':attribute 必须是介于 :min 和 :max 位的数字。', + 'dimensions' => ':attribute 图片尺寸不正确。', + 'distinct' => ':attribute 已经存在。', + 'email' => ':attribute 不是一个合法的邮箱。', + 'ends_with' => ':attribute 必须以 :values 为结尾。', + 'exists' => ':attribute 不存在。', + 'file' => ':attribute 必须是文件。', + 'filled' => ':attribute 不能为空。', + 'gt' => [ + 'numeric' => ':attribute 必须大于 :value。', + 'file' => ':attribute 必须大于 :value KB。', + 'string' => ':attribute 必须多于 :value 个字符。', + 'array' => ':attribute 必须多于 :value 个元素。', + ], + 'gte' => [ + 'numeric' => ':attribute 必须大于或等于 :value。', + 'file' => ':attribute 必须大于或等于 :value KB。', + 'string' => ':attribute 必须多于或等于 :value 个字符。', + 'array' => ':attribute 必须多于或等于 :value 个元素。', + ], + 'image' => ':attribute 必须是图片。', + 'in' => '已选的属性 :attribute 无效。', + 'in_array' => ':attribute 必须在 :other 中。', + 'integer' => ':attribute 必须是整数。', + 'ip' => ':attribute 必须是有效的 IP 地址。', + 'ipv4' => ':attribute 必须是有效的 IPv4 地址。', + 'ipv6' => ':attribute 必须是有效的 IPv6 地址。', + 'json' => ':attribute 必须是正确的 JSON 格式。', + 'lt' => [ + 'numeric' => ':attribute 必须小于 :value。', + 'file' => ':attribute 必须小于 :value KB。', + 'string' => ':attribute 必须少于 :value 个字符。', + 'array' => ':attribute 必须少于 :value 个元素。', + ], + 'lte' => [ + 'numeric' => ':attribute 必须小于或等于 :value。', + 'file' => ':attribute 必须小于或等于 :value KB。', + 'string' => ':attribute 必须少于或等于 :value 个字符。', + 'array' => ':attribute 必须少于或等于 :value 个元素。', + ], + 'max' => [ + 'numeric' => ':attribute 不能大于 :max。', + 'file' => ':attribute 不能大于 :max KB。', + 'string' => ':attribute 不能大于 :max 个字符。', + 'array' => ':attribute 最多只有 :max 个单元。', + ], + 'mimes' => ':attribute 必须是一个 :values 类型的文件。', + 'mimetypes' => ':attribute 必须是一个 :values 类型的文件。', + 'min' => [ + 'numeric' => ':attribute 必须大于等于 :min。', + 'file' => ':attribute 大小不能小于 :min KB。', + 'string' => ':attribute 至少为 :min 个字符。', + 'array' => ':attribute 至少有 :min 个单元。', + ], + 'multiple_of' => ':attribute 必须是 :value 的倍数', + 'not_in' => '已选的属性 :attribute 非法。', + 'not_regex' => ':attribute 的格式错误。', + 'numeric' => ':attribute 必须是一个数字。', + 'password' => '密码错误。', + 'present' => ':attribute 必须存在。', + 'regex' => ':attribute 格式不正确。', + 'required' => ':attribute 不能为空。', + 'required_if' => '当 :other 为 :value 时 :attribute 不能为空。', + 'required_unless' => '当 :other 不为 :values 时 :attribute 不能为空。', + 'required_with' => '当 :values 存在时 :attribute 不能为空。', + 'required_with_all' => '当 :values 存在时 :attribute 不能为空。', + 'required_without' => '当 :values 不存在时 :attribute 不能为空。', + 'required_without_all' => '当 :values 都不存在时 :attribute 不能为空。', + 'same' => ':attribute 和 :other 必须相同。', + 'size' => [ + 'numeric' => ':attribute 大小必须为 :size。', + 'file' => ':attribute 大小必须为 :size KB。', + 'string' => ':attribute 必须是 :size 个字符。', + 'array' => ':attribute 必须为 :size 个单元。', + ], + 'starts_with' => ':attribute 必须以 :values 为开头。', + 'string' => ':attribute 必须是一个字符串。', + 'timezone' => ':attribute 必须是一个合法的时区值。', + 'unique' => ':attribute 已经存在。', + 'uploaded' => ':attribute 上传失败。', + 'url' => ':attribute 格式不正确。', + 'uuid' => ':attribute 必须是有效的 UUID。', + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => '自定义消息', + ], + ], + + 'attributes' => [], +]; diff --git a/client/i18n/lang/ar.json b/client/i18n/lang/ar.json new file mode 100644 index 000000000..764105ca7 --- /dev/null +++ b/client/i18n/lang/ar.json @@ -0,0 +1,21 @@ +{ + "forms": { + "fileInput": { + "chooseFiles": "انقر لاختيار الملفات أو قم بإسقاطها هنا | انقر لاختيار ملف أو قم بإسقاطه هنا", + "sizeLimit": "الحد الأقصى للحجم: {count} ميجابايت لكل ملف", + "uploadingFile": "جاري تحميل الملف الخاص بك..." + }, + "cameraUpload": { + "allowCameraPermission": "السماح بالوصول إلى الكاميرا", + "allowCameraPermissionDescription": "يجب السماح بالوصول إلى الكاميرا قبل التقاط الصور. انتقل إلى إعدادات المتصفح لتمكين الوصول إلى الكاميرا على هذه الصفحة.", + "gotIt": "فهمت!", + "cameraDeviceError": "خطأ في الكاميرا", + "cameraDeviceErrorDescription": "حدث خطأ غير معروف أثناء محاولة تشغيل كاميرا الويب.", + "goBack": "رجوع" + }, + "signatureInput": { + "uploadFileInstead": "تحميل ملف بدلاً من ذلك", + "clear": "مسح" + } + } +} \ No newline at end of file diff --git a/client/i18n/lang/es.json b/client/i18n/lang/es.json new file mode 100644 index 000000000..dd6bd1be8 --- /dev/null +++ b/client/i18n/lang/es.json @@ -0,0 +1,21 @@ +{ + "forms": { + "fileInput": { + "chooseFiles": "Haga clic para elegir archivo(s) o suéltelo(s) aquí | Haga clic para elegir un archivo o suéltelo aquí", + "sizeLimit": "Tamaño límite: {count}MB por archivo", + "uploadingFile": "Subiendo su archivo..." + }, + "cameraUpload": { + "allowCameraPermission": "Permitir acceso a la cámara", + "allowCameraPermissionDescription": "Debe permitir el acceso a la cámara antes de poder tomar fotos. Vaya a la configuración del navegador para habilitar el acceso a la cámara en esta página.", + "gotIt": "¡Entendido!", + "cameraDeviceError": "Error de cámara", + "cameraDeviceErrorDescription": "Se produjo un error desconocido al intentar iniciar el dispositivo de la cámara web.", + "goBack": "Volver" + }, + "signatureInput": { + "uploadFileInstead": "Subir un archivo en su lugar", + "clear": "Borrar" + } + } +} \ No newline at end of file diff --git a/client/i18n/lang/hi.json b/client/i18n/lang/hi.json new file mode 100644 index 000000000..f02fe0bee --- /dev/null +++ b/client/i18n/lang/hi.json @@ -0,0 +1,21 @@ +{ + "forms": { + "fileInput": { + "chooseFiles": "फ़ाइलें चुनने के लिए क्लिक करें या यहां खींचें | फ़ाइल चुनने के लिए क्लिक करें या यहां खींचें", + "sizeLimit": "साइज़ सीमा: प्रति फ़ाइल {count}MB", + "uploadingFile": "आपकी फ़ाइल अपलोड हो रही है..." + }, + "cameraUpload": { + "allowCameraPermission": "कैमरा एक्सेस की अनुमति दें", + "allowCameraPermissionDescription": "फ़ोटो लेने से पहले कैमरा एक्सेस की अनुमति देनी होगी। इस पेज पर कैमरा एक्सेस सक्षम करने के लिए ब्राउज़र सेटिंग्स में जाएं।", + "gotIt": "समझ गया!", + "cameraDeviceError": "कैमरा त्रुटि", + "cameraDeviceErrorDescription": "वेबकैम डिवाइस शुरू करने का प्रयास करते समय एक अज्ञात त्रुटि हुई।", + "goBack": "वापस जाएं" + }, + "signatureInput": { + "uploadFileInstead": "इसके बजाय फ़ाइल अपलोड करें", + "clear": "साफ़ करें" + } + } +} \ No newline at end of file diff --git a/client/i18n/lang/ja.json b/client/i18n/lang/ja.json new file mode 100644 index 000000000..8cb7aa254 --- /dev/null +++ b/client/i18n/lang/ja.json @@ -0,0 +1,21 @@ +{ + "forms": { + "fileInput": { + "chooseFiles": "クリックしてファイルを選択するか、ここにドロップしてください | クリックしてファイルを選択するか、ここにドロップしてください", + "sizeLimit": "サイズ制限:ファイルあたり{count}MB", + "uploadingFile": "ファイルをアップロード中..." + }, + "cameraUpload": { + "allowCameraPermission": "カメラへのアクセスを許可", + "allowCameraPermissionDescription": "写真を撮影する前にカメラへのアクセスを許可する必要があります。ブラウザの設定でこのページのカメラアクセスを有効にしてください。", + "gotIt": "了解しました!", + "cameraDeviceError": "カメラエラー", + "cameraDeviceErrorDescription": "ウェブカメラデバイスの起動時に不明なエラーが発生しました。", + "goBack": "戻る" + }, + "signatureInput": { + "uploadFileInstead": "代わりにファイルをアップロード", + "clear": "クリア" + } + } +} \ No newline at end of file diff --git a/client/i18n/lang/pt.json b/client/i18n/lang/pt.json new file mode 100644 index 000000000..c02f82ef2 --- /dev/null +++ b/client/i18n/lang/pt.json @@ -0,0 +1,21 @@ +{ + "forms": { + "fileInput": { + "chooseFiles": "Clique para escolher arquivo(s) ou solte-os aqui | Clique para escolher um arquivo ou solte-o aqui", + "sizeLimit": "Limite de tamanho: {count}MB por arquivo", + "uploadingFile": "Enviando seu arquivo..." + }, + "cameraUpload": { + "allowCameraPermission": "Permitir acesso à câmera", + "allowCameraPermissionDescription": "Você precisa permitir o acesso à câmera antes de tirar fotos. Vá para as configurações do navegador para ativar o acesso à câmera nesta página.", + "gotIt": "Entendi!", + "cameraDeviceError": "Erro de câmera", + "cameraDeviceErrorDescription": "Ocorreu um erro desconhecido ao tentar iniciar o dispositivo da webcam.", + "goBack": "Voltar" + }, + "signatureInput": { + "uploadFileInstead": "Fazer upload de arquivo em vez disso", + "clear": "Limpar" + } + } +} \ No newline at end of file diff --git a/client/i18n/lang/zh.json b/client/i18n/lang/zh.json new file mode 100644 index 000000000..f834680c5 --- /dev/null +++ b/client/i18n/lang/zh.json @@ -0,0 +1,21 @@ +{ + "forms": { + "fileInput": { + "chooseFiles": "点击选择文件或将文件拖放到此处 | 点击选择文件或将文件拖放到此处", + "sizeLimit": "大小限制:每个文件 {count}MB", + "uploadingFile": "正在上传您的文件..." + }, + "cameraUpload": { + "allowCameraPermission": "允许访问相机", + "allowCameraPermissionDescription": "在拍照之前,您需要允许访问相机。请转到浏览器设置以启用此页面的相机访问权限。", + "gotIt": "明白了!", + "cameraDeviceError": "相机错误", + "cameraDeviceErrorDescription": "尝试启动网络摄像头设备时发生未知错误。", + "goBack": "返回" + }, + "signatureInput": { + "uploadFileInstead": "改为上传文件", + "clear": "清除" + } + } +} \ No newline at end of file diff --git a/client/nuxt.config.ts b/client/nuxt.config.ts index f23a8cd79..047649f63 100644 --- a/client/nuxt.config.ts +++ b/client/nuxt.config.ts @@ -26,6 +26,11 @@ export default defineNuxtConfig({ locales: [ { code: 'en', name: 'English', iso: 'en-US', file: 'en.json' }, { code: 'fr', name: 'French', iso: 'fr-FR', file: 'fr.json' }, + { code: 'hi', name: 'Hindi', iso: 'hi-IN', file: 'hi.json' }, + { code: 'es', name: 'Spanish', iso: 'es-ES', file: 'es.json' }, + { code: 'ar', name: 'Arabic', iso: 'ar-EG', file: 'ar.json' }, + { code: 'zh', name: 'Chinese', iso: 'zh-CN', file: 'zh.json' }, + { code: 'ja', name: 'Japanese', iso: 'ja-JP', file: 'ja.json' }, ], defaultLocale: 'en', lazy: true, From 6876ece57b3f683fae3a9a96261c6ed18151b3f1 Mon Sep 17 00:00:00 2001 From: Chirag Chhatrala Date: Fri, 22 Nov 2024 12:50:57 +0530 Subject: [PATCH 3/3] Support locale for datepicker --- client/components/forms/DateInput.vue | 2 ++ client/components/forms/useFormInput.js | 3 ++- client/components/open/forms/OpenFormField.vue | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/client/components/forms/DateInput.vue b/client/components/forms/DateInput.vue index 60c801f0a..912c333c6 100644 --- a/client/components/forms/DateInput.vue +++ b/client/components/forms/DateInput.vue @@ -71,6 +71,7 @@ :max-date="maxDate" :is-dark="props.isDark" color="form-color" + :locale="props.locale" @update:model-value="updateModelValue" /> diff --git a/client/components/forms/useFormInput.js b/client/components/forms/useFormInput.js index 3ffe97923..8bf7a289c 100644 --- a/client/components/forms/useFormInput.js +++ b/client/components/forms/useFormInput.js @@ -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 = {}) { diff --git a/client/components/open/forms/OpenFormField.vue b/client/components/open/forms/OpenFormField.vue index 1d1747821..de3ae22c0 100644 --- a/client/components/open/forms/OpenFormField.vue +++ b/client/components/open/forms/OpenFormField.vue @@ -328,7 +328,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') {