Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Form Translation #616

Merged
merged 9 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/app/Http/Requests/AnswerFormRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,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);
}
Comment on lines +255 to +258
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Inconsistency found between language support and constant definition

The codebase shows a mismatch between the supported languages and the Form::LANGUAGES constant:

  • Form::LANGUAGES only defines ['en', 'fr']
  • However, the translation files exist for multiple additional languages: de-DE, es, nb-NO, pt-BR, zh-CN

This could lead to potential issues where users cannot select available translated languages because they're not included in the Form::LANGUAGES constant.

  • api/app/Models/Forms/Form.php: Consider updating LANGUAGES constant to include all supported languages with translation files
🔗 Analysis chain

Verify translation files and constant usage

Let's verify the translation setup and constant usage.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for translation files and Form::LANGUAGES constant

# Check for translation files
echo "Checking for translation files..."
fd . resources/lang -t f

# Check Form::LANGUAGES constant usage
echo "Checking Form::LANGUAGES usage..."
rg "LANGUAGES\s*=\s*\[" app/Models/Forms/

# Check other locale usage
echo "Checking other locale setters..."
rg "setLocale|app\(\)->setLocale" app/

Length of output: 545


Script:

#!/bin/bash
# Let's try a broader search without assuming directory structure

# Check for translation files
echo "Checking for translation files..."
fd lang -t d

# Check Form model and its constants
echo "Checking Form model and LANGUAGES constant..."
fd -e php Form.php
rg "class\s+Form" -A 10
rg "LANGUAGES\s*=\s*\["

# Check locale setters across the codebase
echo "Checking locale setters..."
rg "setLocale|app\(\)->setLocale"

Length of output: 43975


Script:

#!/bin/bash
# Let's check the translation files in both api and client directories

# Check for translation files in api
echo "Checking API translation files..."
fd -e php -e json . api/resources/lang/

# Check for translation files in client
echo "Checking client translation files..."
fd -e js -e json . client/i18n/lang/

# Check for actual language usage in forms
echo "Checking language usage in forms..."
rg "language.*=.*" -A 2 api/database/migrations/

Length of output: 1476



Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Cache the country code mapper data

Reading the JSON file on every request is inefficient. Consider caching this data.

// In a service provider or helper class
public static function getCountryCodeMapper()
{
    return cache()->rememberForever('country_code_mapper', function () {
        return json_decode(file_get_contents(resource_path('data/country_code_mapper.json')), true);
    });
}

$receivedData = $this->toArray();
$mergeData = [];
$countryCodeMapper = json_decode(file_get_contents(resource_path('data/country_code_mapper.json')), true);
Expand Down
1 change: 1 addition & 0 deletions api/app/Http/Requests/UserFormRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)],
Expand Down
1 change: 1 addition & 0 deletions api/app/Http/Resources/FormResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ private function getProtectedForm()
'dark_mode' => $this->dark_mode,
'transparent_background' => $this->transparent_background,
'color' => $this->color,
'language' => $this->language,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add language field to the main form response

The language field is only added to protected forms but missing from the main form response in toArray(). This creates an inconsistency in the API response structure.

Add the language field to the main response by including it in the array merge:

 return array_merge(parent::toArray($request), $ownerData, [
     'is_pro' => $this->workspaceIsPro(),
     'is_trialing' => $this->workspaceIsTrialing(),
     'workspace_id' => $this->workspace_id,
     'workspace' => new WorkspaceResource($this->getWorkspace()),
     'is_closed' => $this->is_closed,
     'is_password_protected' => false,
     'has_password' => $this->has_password,
     'max_number_of_submissions_reached' => $this->max_number_of_submissions_reached,
     'form_pending_submission_key' => $this->form_pending_submission_key,
     'max_file_size' => $this->max_file_size / 1000000,
+    'language' => $this->language,
 ]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'language' => $this->language,
return array_merge(parent::toArray($request), $ownerData, [
'is_pro' => $this->workspaceIsPro(),
'is_trialing' => $this->workspaceIsTrialing(),
'workspace_id' => $this->workspace_id,
'workspace' => new WorkspaceResource($this->getWorkspace()),
'is_closed' => $this->is_closed,
'is_password_protected' => false,
'has_password' => $this->has_password,
'max_number_of_submissions_reached' => $this->max_number_of_submissions_reached,
'form_pending_submission_key' => $this->form_pending_submission_key,
'max_file_size' => $this->max_file_size / 1000000,
'language' => $this->language,
]);

'theme' => $this->theme,
'is_password_protected' => true,
'has_password' => $this->has_password,
Expand Down
3 changes: 3 additions & 0 deletions api/app/Models/Forms/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class Form extends Model implements CachableAttributes

public const VISIBILITY = ['public', 'draft', 'closed'];

public const LANGUAGES = ['en', 'fr', 'hi', 'es', 'ar', 'zh', 'ja'];

protected $fillable = [
'workspace_id',
'creator_id',
Expand All @@ -52,6 +54,7 @@ class Form extends Model implements CachableAttributes
'visibility',

// Customization
'language',
'font_family',
'custom_domain',
'size',
Expand Down
6 changes: 6 additions & 0 deletions api/config/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@

'locales' => [
'en' => 'EN',
'fr' => 'FR',
'hi' => 'HI',
'es' => 'ES',
'ar' => 'AR',
'zh' => 'ZH',
'ja' => 'JA',
],

/*
Expand Down
1 change: 1 addition & 0 deletions api/database/factories/FormFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class () extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('forms', function (Blueprint $table) {
$table->string('language')->default('en');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('forms', function (Blueprint $table) {
$table->dropColumn('language');
});
}
};
117 changes: 117 additions & 0 deletions api/resources/lang/ar/validation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

return [
'accepted' => 'يجب قبول :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' => [],
];
117 changes: 117 additions & 0 deletions api/resources/lang/fr/validation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

return [
'accepted' => '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' => [],
];
Loading
Loading