2026-07-03 17:12:25 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace App\Enums\Workspace;
|
|
|
|
|
|
|
|
|
|
/**
|
2026-07-03 21:52:45 +00:00
|
|
|
* 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.
|
2026-07-03 17:12:25 +00:00
|
|
|
*
|
|
|
|
|
* The string value is the language code stored on the workspace and passed
|
2026-07-03 21:52:45 +00:00
|
|
|
* straight to the content prompts (`content_language`); the same codes also back
|
2026-07-03 21:57:09 +00:00
|
|
|
* the application's UI locales, so `direction()` drives the document `dir` attribute.
|
2026-07-03 17:12:25 +00:00
|
|
|
*/
|
|
|
|
|
enum ContentLanguage: string
|
|
|
|
|
{
|
|
|
|
|
case English = 'en';
|
2026-08-05 22:45:30 +00:00
|
|
|
case Ukrainian = 'uk';
|
2026-07-03 17:12:25 +00:00
|
|
|
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',
|
2026-08-05 22:45:30 +00:00
|
|
|
self::Ukrainian => 'Українська',
|
2026-07-03 17:12:25 +00:00
|
|
|
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',
|
2026-08-05 22:45:30 +00:00
|
|
|
self::Ukrainian => 'Ukrainian',
|
2026-07-03 17:12:25 +00:00
|
|
|
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',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-07-03 21:57:09 +00:00
|
|
|
* The text direction (`ltr` or `rtl`) for the document root when this is the
|
|
|
|
|
* active UI locale — only Arabic is written right to left.
|
Add UI localization infrastructure and RTL support for 12 new locales
Register the 12 additional languages (fr, de, it, nl, pl, el, ja, ko, zh,
ru, tr, ar) as available UI locales so the language switcher and the API
accept them, keeping the set in lockstep with the ContentLanguage enum.
- config/languages.php lists all 15 UI locales with their native names.
- ContentLanguage::isRtl() drives the document `dir`; SetLocale shares it
to the Blade root and HandleInertiaRequests shares it to Inertia, and
app.ts mirrors it on SPA navigations so RTL locales lay out correctly.
- dayjs imports the 12 new locales so dates localize instead of falling
back to English.
- A LocalizationParityTest guards against key drift: every locale must
ship every base translation file with exactly the keys of lang/en.
2026-07-03 18:38:40 +00:00
|
|
|
*/
|
2026-07-03 21:57:09 +00:00
|
|
|
public function direction(): string
|
Add UI localization infrastructure and RTL support for 12 new locales
Register the 12 additional languages (fr, de, it, nl, pl, el, ja, ko, zh,
ru, tr, ar) as available UI locales so the language switcher and the API
accept them, keeping the set in lockstep with the ContentLanguage enum.
- config/languages.php lists all 15 UI locales with their native names.
- ContentLanguage::isRtl() drives the document `dir`; SetLocale shares it
to the Blade root and HandleInertiaRequests shares it to Inertia, and
app.ts mirrors it on SPA navigations so RTL locales lay out correctly.
- dayjs imports the 12 new locales so dates localize instead of falling
back to English.
- A LocalizationParityTest guards against key drift: every locale must
ship every base translation file with exactly the keys of lang/en.
2026-07-03 18:38:40 +00:00
|
|
|
{
|
2026-07-03 21:57:09 +00:00
|
|
|
return $this === self::Arabic ? 'rtl' : 'ltr';
|
Add UI localization infrastructure and RTL support for 12 new locales
Register the 12 additional languages (fr, de, it, nl, pl, el, ja, ko, zh,
ru, tr, ar) as available UI locales so the language switcher and the API
accept them, keeping the set in lockstep with the ContentLanguage enum.
- config/languages.php lists all 15 UI locales with their native names.
- ContentLanguage::isRtl() drives the document `dir`; SetLocale shares it
to the Blade root and HandleInertiaRequests shares it to Inertia, and
app.ts mirrors it on SPA navigations so RTL locales lay out correctly.
- dayjs imports the 12 new locales so dates localize instead of falling
back to English.
- A LocalizationParityTest guards against key drift: every locale must
ship every base translation file with exactly the keys of lang/en.
2026-07-03 18:38:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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.
|
2026-07-03 17:12:25 +00:00
|
|
|
*/
|
|
|
|
|
public static function fromHtmlLang(string $lang): ?self
|
|
|
|
|
{
|
Add UI localization infrastructure and RTL support for 12 new locales
Register the 12 additional languages (fr, de, it, nl, pl, el, ja, ko, zh,
ru, tr, ar) as available UI locales so the language switcher and the API
accept them, keeping the set in lockstep with the ContentLanguage enum.
- config/languages.php lists all 15 UI locales with their native names.
- ContentLanguage::isRtl() drives the document `dir`; SetLocale shares it
to the Blade root and HandleInertiaRequests shares it to Inertia, and
app.ts mirrors it on SPA navigations so RTL locales lay out correctly.
- dayjs imports the 12 new locales so dates localize instead of falling
back to English.
- A LocalizationParityTest guards against key drift: every locale must
ship every base translation file with exactly the keys of lang/en.
2026-07-03 18:38:40 +00:00
|
|
|
$subtag = self::primarySubtag($lang);
|
2026-07-03 17:12:25 +00:00
|
|
|
|
Add UI localization infrastructure and RTL support for 12 new locales
Register the 12 additional languages (fr, de, it, nl, pl, el, ja, ko, zh,
ru, tr, ar) as available UI locales so the language switcher and the API
accept them, keeping the set in lockstep with the ContentLanguage enum.
- config/languages.php lists all 15 UI locales with their native names.
- ContentLanguage::isRtl() drives the document `dir`; SetLocale shares it
to the Blade root and HandleInertiaRequests shares it to Inertia, and
app.ts mirrors it on SPA navigations so RTL locales lay out correctly.
- dayjs imports the 12 new locales so dates localize instead of falling
back to English.
- A LocalizationParityTest guards against key drift: every locale must
ship every base translation file with exactly the keys of lang/en.
2026-07-03 18:38:40 +00:00
|
|
|
if (strlen($subtag) < 2) {
|
2026-07-03 17:12:25 +00:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (self::cases() as $language) {
|
Add UI localization infrastructure and RTL support for 12 new locales
Register the 12 additional languages (fr, de, it, nl, pl, el, ja, ko, zh,
ru, tr, ar) as available UI locales so the language switcher and the API
accept them, keeping the set in lockstep with the ContentLanguage enum.
- config/languages.php lists all 15 UI locales with their native names.
- ContentLanguage::isRtl() drives the document `dir`; SetLocale shares it
to the Blade root and HandleInertiaRequests shares it to Inertia, and
app.ts mirrors it on SPA navigations so RTL locales lay out correctly.
- dayjs imports the 12 new locales so dates localize instead of falling
back to English.
- A LocalizationParityTest guards against key drift: every locale must
ship every base translation file with exactly the keys of lang/en.
2026-07-03 18:38:40 +00:00
|
|
|
if (self::primarySubtag($language->value) === $subtag) {
|
2026-07-03 17:12:25 +00:00
|
|
|
return $language;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
Add UI localization infrastructure and RTL support for 12 new locales
Register the 12 additional languages (fr, de, it, nl, pl, el, ja, ko, zh,
ru, tr, ar) as available UI locales so the language switcher and the API
accept them, keeping the set in lockstep with the ContentLanguage enum.
- config/languages.php lists all 15 UI locales with their native names.
- ContentLanguage::isRtl() drives the document `dir`; SetLocale shares it
to the Blade root and HandleInertiaRequests shares it to Inertia, and
app.ts mirrors it on SPA navigations so RTL locales lay out correctly.
- dayjs imports the 12 new locales so dates localize instead of falling
back to English.
- A LocalizationParityTest guards against key drift: every locale must
ship every base translation file with exactly the keys of lang/en.
2026-07-03 18:38:40 +00:00
|
|
|
/**
|
|
|
|
|
* 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]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-03 17:12:25 +00:00
|
|
|
/**
|
|
|
|
|
* @return array<int, string>
|
|
|
|
|
*/
|
|
|
|
|
public static function values(): array
|
|
|
|
|
{
|
|
|
|
|
return array_map(fn (self $language) => $language->value, self::cases());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-07-03 21:52:45 +00:00
|
|
|
* @return array<int, array{value: string, label: string, englishName: string}>
|
2026-07-03 17:12:25 +00:00
|
|
|
*/
|
|
|
|
|
public static function options(): array
|
|
|
|
|
{
|
|
|
|
|
return array_map(
|
2026-07-03 21:52:45 +00:00
|
|
|
fn (self $language) => [
|
|
|
|
|
'value' => $language->value,
|
|
|
|
|
'label' => $language->label(),
|
|
|
|
|
'englishName' => $language->englishName(),
|
|
|
|
|
],
|
2026-07-03 17:12:25 +00:00
|
|
|
self::cases(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|