feat: localizations
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -2,14 +2,15 @@
|
||||
|
||||
namespace KTXC\Controllers;
|
||||
|
||||
use KTXC\Http\Request\Request;
|
||||
use KTXC\Http\Response\JsonResponse;
|
||||
use KTXC\L10N\LocaleResolver;
|
||||
use KTXC\Module\ModuleManager;
|
||||
use KTXC\Security\Authorization\PermissionChecker;
|
||||
use KTXC\Service\UserAccountsService;
|
||||
use KTXC\SessionIdentity;
|
||||
use KTXF\Controller\ControllerAbstract;
|
||||
use KTXC\SessionTenant;
|
||||
use KTXF\Module\ModuleBrowserInterface;
|
||||
use KTXF\Routing\Attributes\AuthenticatedRoute;
|
||||
|
||||
class InitController extends ControllerAbstract
|
||||
@@ -20,10 +21,11 @@ class InitController extends ControllerAbstract
|
||||
private readonly ModuleManager $moduleManager,
|
||||
private readonly UserAccountsService $userService,
|
||||
private readonly PermissionChecker $permissionChecker,
|
||||
private readonly LocaleResolver $localeResolver,
|
||||
) {}
|
||||
|
||||
#[AuthenticatedRoute('/init', name: 'init', methods: ['GET'])]
|
||||
public function index(): JsonResponse {
|
||||
public function index(Request $request): JsonResponse {
|
||||
|
||||
$configuration = [];
|
||||
|
||||
@@ -43,6 +45,13 @@ class InitController extends ControllerAbstract
|
||||
}
|
||||
}
|
||||
|
||||
// localization
|
||||
$configuration['l10n'] = [
|
||||
'locale' => $this->localeResolver->resolve($request),
|
||||
'fallback' => LocaleResolver::FALLBACK,
|
||||
'available' => $this->localeResolver->available(),
|
||||
];
|
||||
|
||||
// tenant
|
||||
$configuration['tenant'] = [
|
||||
'id' => $this->tenant->identifier(),
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace KTXC\L10N;
|
||||
|
||||
use DI\Attribute\Inject;
|
||||
use KTXC\Http\Request\Request;
|
||||
use KTXC\Service\UserAccountsService;
|
||||
|
||||
/**
|
||||
* Resolves the effective UI locale for the current user.
|
||||
*
|
||||
* Resolution chain: user setting (core.locale) -> Accept-Language
|
||||
* negotiation -> fallback. Tenant default slots in between once tenant
|
||||
* settings expose one (Phase 3).
|
||||
*/
|
||||
class LocaleResolver
|
||||
{
|
||||
public const FALLBACK = 'en';
|
||||
public const SETTING_KEY = 'core.locale';
|
||||
|
||||
public function __construct(
|
||||
private readonly UserAccountsService $userService,
|
||||
#[Inject('rootDir')] private readonly string $rootDir,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Locales with a core catalog on disk (public/l10n/*.json), always
|
||||
* including the fallback. This backs the language picker, so a locale
|
||||
* is only offered when it has at least core-shell coverage.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function available(): array
|
||||
{
|
||||
$locales = [self::FALLBACK];
|
||||
foreach (glob($this->rootDir . '/public/l10n/*.json') ?: [] as $file) {
|
||||
$locales[] = basename($file, '.json');
|
||||
}
|
||||
$locales = array_values(array_unique($locales));
|
||||
sort($locales);
|
||||
return $locales;
|
||||
}
|
||||
|
||||
public function resolve(Request $request): string
|
||||
{
|
||||
$available = $this->available();
|
||||
|
||||
// 1. explicit user preference
|
||||
$settings = $this->userService->fetchSettings([self::SETTING_KEY]);
|
||||
$preference = $settings[self::SETTING_KEY] ?? null;
|
||||
if (is_string($preference) && in_array($preference, $available, true)) {
|
||||
return $preference;
|
||||
}
|
||||
|
||||
// 2. Accept-Language negotiation. getPreferredLanguage() returns the
|
||||
// first candidate when nothing matches, so the fallback leads the
|
||||
// list; it also normalizes to underscore form (pt_BR) while catalogs
|
||||
// use BCP-47 hyphens.
|
||||
$candidates = array_merge([self::FALLBACK], array_diff($available, [self::FALLBACK]));
|
||||
$negotiated = $request->getPreferredLanguage($candidates);
|
||||
if ($negotiated !== null) {
|
||||
$negotiated = str_replace('_', '-', $negotiated);
|
||||
if (in_array($negotiated, $available, true)) {
|
||||
return $negotiated;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. fallback
|
||||
return self::FALLBACK;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user