* Default AI content language to Ukrainian * Keep English as the default content language * refactor: update content language handling in workspace creation - Changed the default content language in CreateWorkspace to inherit the app's locale instead of defaulting to English. - Updated related tests to reflect this change, ensuring that the content language aligns with the application's current locale settings. - Cleaned up unnecessary code in the BrandTab component for better readability. * Add Ukrainian as a full platform UI locale. Wire uk into languages config with complete lang/uk translations and restore ContentLanguage↔UI parity checks. Co-authored-by: Cursor <cursoragent@cursor.com> * Wire Ukrainian into dayjs and locale coverage tests. Import the uk dayjs locale with Monday week-start and cover uk/uk-UA in brand autofill and SetLocale assertions. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden Ukrainian UI and AI content-language coverage. Cover generator/reviewer/humanizer/image prompts, workspace pickers, persona labels, UI locale switch, and README for uk as a first-class platform language. Co-authored-by: Cursor <cursoragent@cursor.com> * Add Ukrainian Pinterest form strings after main merge. Restore lang/uk posts.php key parity for the new pin title and destination link fields. Co-authored-by: Cursor <cursoragent@cursor.com> * List every supported UI language in the README. Replace the abbreviated multi-language blurb with the full locale set from config. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Paulo Castellano <paulo@castellanos.llc> Co-authored-by: Cursor <cursoragent@cursor.com>
149 lines
4.5 KiB
PHP
149 lines
4.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Enums\Workspace;
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* 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.
|
|
*/
|
|
enum ContentLanguage: string
|
|
{
|
|
case English = 'en';
|
|
case Ukrainian = 'uk';
|
|
case PortugueseBrazil = 'pt-BR';
|
|
case Spanish = 'es';
|
|
case French = 'fr';
|
|
case German = 'de';
|
|
case Italian = 'it';
|
|
case Dutch = 'nl';
|
|
case Polish = 'pl';
|
|
case Greek = 'el';
|
|
case Japanese = 'ja';
|
|
case Korean = 'ko';
|
|
case Chinese = 'zh';
|
|
case Russian = 'ru';
|
|
case Turkish = 'tr';
|
|
case Arabic = 'ar';
|
|
|
|
public const DEFAULT = self::English;
|
|
|
|
/**
|
|
* The language's own name, shown in the content-language picker.
|
|
*/
|
|
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',
|
|
self::German => 'Deutsch',
|
|
self::Italian => 'Italiano',
|
|
self::Dutch => 'Nederlands',
|
|
self::Polish => 'Polski',
|
|
self::Greek => 'Ελληνικά',
|
|
self::Japanese => '日本語',
|
|
self::Korean => '한국어',
|
|
self::Chinese => '中文',
|
|
self::Russian => 'Русский',
|
|
self::Turkish => 'Türkçe',
|
|
self::Arabic => 'العربية',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The English name of the language, injected into AI image prompts so the
|
|
* in-image text is rendered in the workspace's content language.
|
|
*/
|
|
public function englishName(): string
|
|
{
|
|
return match ($this) {
|
|
self::English => 'English',
|
|
self::Ukrainian => 'Ukrainian',
|
|
self::PortugueseBrazil => 'Brazilian Portuguese',
|
|
self::Spanish => 'Spanish',
|
|
self::French => 'French',
|
|
self::German => 'German',
|
|
self::Italian => 'Italian',
|
|
self::Dutch => 'Dutch',
|
|
self::Polish => 'Polish',
|
|
self::Greek => 'Greek',
|
|
self::Japanese => 'Japanese',
|
|
self::Korean => 'Korean',
|
|
self::Chinese => 'Chinese',
|
|
self::Russian => 'Russian',
|
|
self::Turkish => 'Turkish',
|
|
self::Arabic => 'Arabic',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The text direction (`ltr` or `rtl`) for the document root when this is the
|
|
* active UI locale — only Arabic is written right to left.
|
|
*/
|
|
public function direction(): string
|
|
{
|
|
return $this === self::Arabic ? 'rtl' : 'ltr';
|
|
}
|
|
|
|
/**
|
|
* Resolve a raw `<html lang>` value (e.g. "pt-PT", "zh-Hans") to a supported
|
|
* language by matching its primary subtag, or null if none is supported.
|
|
*/
|
|
public static function fromHtmlLang(string $lang): ?self
|
|
{
|
|
$subtag = self::primarySubtag($lang);
|
|
|
|
if (strlen($subtag) < 2) {
|
|
return null;
|
|
}
|
|
|
|
foreach (self::cases() as $language) {
|
|
if (self::primarySubtag($language->value) === $subtag) {
|
|
return $language;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* The lowercased primary subtag of a BCP 47 language tag ("pt-BR" => "pt").
|
|
*/
|
|
private static function primarySubtag(string $tag): string
|
|
{
|
|
return strtolower(explode('-', trim($tag), 2)[0]);
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
public static function values(): array
|
|
{
|
|
return array_map(fn (self $language) => $language->value, self::cases());
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array{value: string, label: string, englishName: string}>
|
|
*/
|
|
public static function options(): array
|
|
{
|
|
return array_map(
|
|
fn (self $language) => [
|
|
'value' => $language->value,
|
|
'label' => $language->label(),
|
|
'englishName' => $language->englishName(),
|
|
],
|
|
self::cases(),
|
|
);
|
|
}
|
|
}
|