From 55add700462279e82cb3a6fded5af66360d539da Mon Sep 17 00:00:00 2001 From: axi Date: Fri, 31 Jul 2026 20:30:02 +0300 Subject: [PATCH] Default AI content language to Ukrainian --- app/Actions/Workspace/CreateWorkspace.php | 3 ++- app/Enums/Workspace/ContentLanguage.php | 12 ++++++++---- resources/js/components/settings/BrandTab.vue | 2 +- resources/js/pages/workspaces/Create.vue | 2 +- .../Actions/Workspace/CreateWorkspaceTest.php | 6 +++--- tests/Feature/LocalizationParityTest.php | 4 ++-- tests/Unit/Ai/Agents/PostContentGeneratorTest.php | 10 ++++++++++ tests/Unit/Enums/Workspace/ContentLanguageTest.php | 12 ++++++++---- 8 files changed, 35 insertions(+), 16 deletions(-) diff --git a/app/Actions/Workspace/CreateWorkspace.php b/app/Actions/Workspace/CreateWorkspace.php index 3c7c2df0e..2c54bd74c 100644 --- a/app/Actions/Workspace/CreateWorkspace.php +++ b/app/Actions/Workspace/CreateWorkspace.php @@ -5,6 +5,7 @@ namespace App\Actions\Workspace; use App\Enums\UserWorkspace\Role; +use App\Enums\Workspace\ContentLanguage; use App\Models\User; use App\Models\Workspace; use Illuminate\Support\Facades\DB; @@ -24,7 +25,7 @@ public static function execute(User $user, array $data): Workspace 'brand_color' => data_get($data, 'brand_color'), 'background_color' => data_get($data, 'background_color'), 'text_color' => data_get($data, 'text_color'), - 'content_language' => data_get($data, 'content_language', app()->getLocale()), + 'content_language' => data_get($data, 'content_language', ContentLanguage::DEFAULT->value), ], static fn ($value): bool => $value !== null); $workspace = DB::transaction(function () use ($user, $attributes): Workspace { diff --git a/app/Enums/Workspace/ContentLanguage.php b/app/Enums/Workspace/ContentLanguage.php index ecb7f3e24..6e2203739 100644 --- a/app/Enums/Workspace/ContentLanguage.php +++ b/app/Enums/Workspace/ContentLanguage.php @@ -8,15 +8,17 @@ * The set of languages the app supports, and the single source of truth for it: * request validation, the brand analyzer's structured-output enum, homepage * language detection, the AI image prompt's language name, the content-language - * picker options, and the right-to-left direction of the UI all derive from it. + * picker options, and the text direction used by supported UI locales all derive + * from it. * * The string value is the language code stored on the workspace and passed - * straight to the content prompts (`content_language`); the same codes also back - * the application's UI locales, so `direction()` drives the document `dir` attribute. + * straight to the content prompts (`content_language`). UI locales are a subset + * configured separately in `config/languages.php`. */ enum ContentLanguage: string { case English = 'en'; + case Ukrainian = 'uk'; case PortugueseBrazil = 'pt-BR'; case Spanish = 'es'; case French = 'fr'; @@ -32,7 +34,7 @@ enum ContentLanguage: string case Turkish = 'tr'; case Arabic = 'ar'; - public const DEFAULT = self::English; + public const DEFAULT = self::Ukrainian; /** * The language's own name, shown in the content-language picker. @@ -41,6 +43,7 @@ public function label(): string { return match ($this) { self::English => 'English', + self::Ukrainian => 'Українська', self::PortugueseBrazil => 'Português (Brasil)', self::Spanish => 'Español', self::French => 'Français', @@ -66,6 +69,7 @@ public function englishName(): string { return match ($this) { self::English => 'English', + self::Ukrainian => 'Ukrainian', self::PortugueseBrazil => 'Brazilian Portuguese', self::Spanish => 'Spanish', self::French => 'French', diff --git a/resources/js/components/settings/BrandTab.vue b/resources/js/components/settings/BrandTab.vue index 31b707383..af3960db6 100644 --- a/resources/js/components/settings/BrandTab.vue +++ b/resources/js/components/settings/BrandTab.vue @@ -39,7 +39,7 @@ const form = useForm({ text_color: props.workspace.text_color, brand_font: props.workspace.brand_font ?? 'Inter', image_style: props.workspace.image_style ?? 'cinematic', - content_language: props.workspace.content_language ?? 'en', + content_language: props.workspace.content_language ?? 'uk', logo_url: '' as string | null, }); diff --git a/resources/js/pages/workspaces/Create.vue b/resources/js/pages/workspaces/Create.vue index dbd4c2b60..6a2866b0c 100644 --- a/resources/js/pages/workspaces/Create.vue +++ b/resources/js/pages/workspaces/Create.vue @@ -24,7 +24,7 @@ const form = useForm({ text_color: null as string | null, brand_font: 'Inter', image_style: 'cinematic', - content_language: 'en', + content_language: 'uk', logo_url: '' as string | null, }); diff --git a/tests/Feature/Actions/Workspace/CreateWorkspaceTest.php b/tests/Feature/Actions/Workspace/CreateWorkspaceTest.php index 7c57efd32..f67c8a2b5 100644 --- a/tests/Feature/Actions/Workspace/CreateWorkspaceTest.php +++ b/tests/Feature/Actions/Workspace/CreateWorkspaceTest.php @@ -42,7 +42,7 @@ expect($member?->pivot->role)->toBe(Role::Admin->value); }); -test('CreateWorkspace inherits the app locale as content_language when none is given', function () { +test('CreateWorkspace defaults content generation to Ukrainian when none is given', function () { app()->setLocale('pt-BR'); $account = Account::factory()->create(); @@ -50,10 +50,10 @@ $workspace = CreateWorkspace::execute($user, ['name' => 'Acme']); - expect($workspace->content_language)->toBe('pt-BR'); + expect($workspace->content_language)->toBe('uk'); }); -test('CreateWorkspace keeps an explicit content_language over the app locale', function () { +test('CreateWorkspace keeps an explicit content_language over the default', function () { app()->setLocale('pt-BR'); $account = Account::factory()->create(); diff --git a/tests/Feature/LocalizationParityTest.php b/tests/Feature/LocalizationParityTest.php index a6701f505..cf839a5e2 100644 --- a/tests/Feature/LocalizationParityTest.php +++ b/tests/Feature/LocalizationParityTest.php @@ -5,9 +5,9 @@ use App\Enums\Workspace\ContentLanguage; use Illuminate\Support\Arr; -test('every UI language in config matches the ContentLanguage enum', function () { +test('every UI language is also a supported content language', function () { expect(array_keys(config('languages.available'))) - ->toEqualCanonicalizing(ContentLanguage::values()); + ->each->toBeIn(ContentLanguage::values()); }); test('the default UI language is a supported content language', function () { diff --git a/tests/Unit/Ai/Agents/PostContentGeneratorTest.php b/tests/Unit/Ai/Agents/PostContentGeneratorTest.php index b00476851..95eb4aa2b 100644 --- a/tests/Unit/Ai/Agents/PostContentGeneratorTest.php +++ b/tests/Unit/Ai/Agents/PostContentGeneratorTest.php @@ -4,9 +4,19 @@ use App\Ai\Agents\PostContentGenerator; use App\Enums\Ai\GeneratorFormat; +use App\Enums\Workspace\ContentLanguage; use App\Models\Workspace; use Illuminate\JsonSchema\JsonSchemaTypeFactory; +test('instructions use Ukrainian for the default content language', function () { + $workspace = Workspace::factory()->make([ + 'content_language' => ContentLanguage::DEFAULT->value, + ]); + + expect((new PostContentGenerator(workspace: $workspace))->instructions()) + ->toContain('Write the output in the language with code: uk.'); +}); + test('instructions render brand context', function () { $workspace = Workspace::factory()->make([ 'name' => 'TryPost', diff --git a/tests/Unit/Enums/Workspace/ContentLanguageTest.php b/tests/Unit/Enums/Workspace/ContentLanguageTest.php index b816c52e7..1fe924345 100644 --- a/tests/Unit/Enums/Workspace/ContentLanguageTest.php +++ b/tests/Unit/Enums/Workspace/ContentLanguageTest.php @@ -6,14 +6,14 @@ test('values exposes every supported content-language code', function () { expect(ContentLanguage::values())->toBe([ - 'en', 'pt-BR', 'es', 'fr', 'de', 'it', 'nl', + 'en', 'uk', 'pt-BR', 'es', 'fr', 'de', 'it', 'nl', 'pl', 'el', 'ja', 'ko', 'zh', 'ru', 'tr', 'ar', ]); }); -test('default language is English', function () { - expect(ContentLanguage::DEFAULT)->toBe(ContentLanguage::English); - expect(ContentLanguage::DEFAULT->value)->toBe('en'); +test('default content language is Ukrainian', function () { + expect(ContentLanguage::DEFAULT)->toBe(ContentLanguage::Ukrainian); + expect(ContentLanguage::DEFAULT->value)->toBe('uk'); }); test('options pairs each code with its native and English label', function () { @@ -21,6 +21,7 @@ expect($options)->toHaveCount(count(ContentLanguage::cases())); expect($options[0])->toBe(['value' => 'en', 'label' => 'English', 'englishName' => 'English']); + expect($options)->toContain(['value' => 'uk', 'label' => 'Українська', 'englishName' => 'Ukrainian']); expect($options)->toContain(['value' => 'pt-BR', 'label' => 'Português (Brasil)', 'englishName' => 'Brazilian Portuguese']); expect($options)->toContain(['value' => 'ja', 'label' => '日本語', 'englishName' => 'Japanese']); }); @@ -29,6 +30,7 @@ expect($language->englishName())->toBe($expected); })->with([ [ContentLanguage::English, 'English'], + [ContentLanguage::Ukrainian, 'Ukrainian'], [ContentLanguage::PortugueseBrazil, 'Brazilian Portuguese'], [ContentLanguage::Spanish, 'Spanish'], [ContentLanguage::French, 'French'], @@ -55,6 +57,7 @@ expect($language->label())->toBe($expected); })->with([ [ContentLanguage::English, 'English'], + [ContentLanguage::Ukrainian, 'Українська'], [ContentLanguage::PortugueseBrazil, 'Português (Brasil)'], [ContentLanguage::Spanish, 'Español'], [ContentLanguage::French, 'Français'], @@ -87,6 +90,7 @@ ['pt', ContentLanguage::PortugueseBrazil], ['pt-PT', ContentLanguage::PortugueseBrazil], ['en-US', ContentLanguage::English], + ['uk-UA', ContentLanguage::Ukrainian], ['es-MX', ContentLanguage::Spanish], ['fr', ContentLanguage::French], ['ja-JP', ContentLanguage::Japanese],