Optimized locale handling

This commit is contained in:
Bernd Bestel 2026-04-21 18:47:18 +02:00
parent 4307bbf5d5
commit c3122609f2
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
8 changed files with 68 additions and 71 deletions

View File

@ -105,15 +105,7 @@ if (!empty(GROCY_BASE_PATH))
$app->setBasePath(GROCY_BASE_PATH); $app->setBasePath(GROCY_BASE_PATH);
} }
if (GROCY_MODE === 'production' || GROCY_MODE === 'dev')
{
$app->add(new LocaleMiddleware($container, $app->getResponseFactory())); $app->add(new LocaleMiddleware($container, $app->getResponseFactory()));
}
else
{
define('GROCY_LOCALE', GROCY_DEFAULT_LOCALE);
}
$authMiddlewareClass = GROCY_AUTH_CLASS; $authMiddlewareClass = GROCY_AUTH_CLASS;
$app->add(new $authMiddlewareClass($container, $app->getResponseFactory())); $app->add(new $authMiddlewareClass($container, $app->getResponseFactory()));

View File

@ -55,6 +55,7 @@
- Fixed accent insensitive searching using the general table search field was broken - Fixed accent insensitive searching using the general table search field was broken
- Fixed that it wasn't possible to log in using passwords containing special escape sequences (e.g. `<<`) - Fixed that it wasn't possible to log in using passwords containing special escape sequences (e.g. `<<`)
- Fixed that the initially created location and quantity units weren't localized (only applies to new installations)
### API ### API

View File

@ -21,6 +21,10 @@ Setting('MODE', 'production');
// The directory name of one of the available localization folders // The directory name of one of the available localization folders
// in the "/localization" directory (e.g. "en" or "de") // in the "/localization" directory (e.g. "en" or "de")
// Grocy uses the first available locale / setting in this order
// 1. Browser prefered locale
// 2. The one set in user settings
// 3. The one defined here below
Setting('DEFAULT_LOCALE', 'en'); Setting('DEFAULT_LOCALE', 'en');
// This is used to define the first day of a week for calendar views, // This is used to define the first day of a week for calendar views,

View File

@ -27,19 +27,6 @@ msgstr ""
msgid "Tinned food cupboard" msgid "Tinned food cupboard"
msgstr "" msgstr ""
msgid "Fridge"
msgstr ""
msgid "Piece"
msgid_plural "Pieces"
msgstr[0] ""
msgstr[1] ""
msgid "Pack"
msgid_plural "Packs"
msgstr[0] ""
msgstr[1] ""
msgid "Glass" msgid "Glass"
msgid_plural "Glasses" msgid_plural "Glasses"
msgstr[0] "" msgstr[0] ""

View File

@ -2473,3 +2473,19 @@ msgstr ""
msgid "After this product was once in stock and when the desired quantity unit cannot be selected here, first create a corresponding unit conversion" msgid "After this product was once in stock and when the desired quantity unit cannot be selected here, first create a corresponding unit conversion"
msgstr "" msgstr ""
# Default / initially created location
msgid "Fridge"
msgstr ""
# Default / initially created quantity unit
msgid "Piece"
msgid_plural "Pieces"
msgstr[0] ""
msgstr[1] ""
# Default / initially created quantity unit
msgid "Pack"
msgid_plural "Packs"
msgstr[0] ""
msgstr[1] ""

View File

@ -11,16 +11,24 @@ class LocaleMiddleware extends BaseMiddleware
{ {
public function __invoke(Request $request, RequestHandler $handler): Response public function __invoke(Request $request, RequestHandler $handler): Response
{ {
$locale = $this->getLocale($request); define('GROCY_LOCALE', $this->GetLocale($request));
define('GROCY_LOCALE', $locale);
return $handler->handle($request); return $handler->handle($request);
} }
protected function getLocale(Request $request) private function GetLocale(Request $request)
{ {
// demo and prerelease modes are fixed to the default locale
if (GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease')
{
return GROCY_DEFAULT_LOCALE;
}
// Prefer user setting
if (defined('GROCY_AUTHENTICATED') && GROCY_AUTHENTICATED) if (defined('GROCY_AUTHENTICATED') && GROCY_AUTHENTICATED)
{ {
$locale = UsersService::GetInstance()->GetUserSetting(GROCY_USER_ID, 'locale'); $locale = UsersService::GetInstance()->GetUserSetting(GROCY_USER_ID, 'locale');
if (isset($locale) && !empty($locale)) if (isset($locale) && !empty($locale))
{ {
if (in_array($locale, scandir(__DIR__ . '/../localization'))) if (in_array($locale, scandir(__DIR__ . '/../localization')))
@ -30,11 +38,9 @@ class LocaleMiddleware extends BaseMiddleware
} }
} }
$langs = implode(',', $request->getHeader('Accept-Language')); // Otherwise use Browser prefered locale
$browserPreferedLocales = array_reduce(
// Src: https://gist.github.com/spolischook/0cde9c6286415cddc088 explode(',', implode(',', $request->getHeader('Accept-Language'))),
$prefLocales = array_reduce(
explode(',', $langs),
function ($res, $el) function ($res, $el)
{ {
list($l, $q) = array_merge(explode(';q=', $el), [1]); list($l, $q) = array_merge(explode(';q=', $el), [1]);
@ -43,10 +49,10 @@ class LocaleMiddleware extends BaseMiddleware
}, },
[] []
); );
arsort($prefLocales); arsort($browserPreferedLocales);
$availableLocales = scandir(__DIR__ . '/../localization'); $availableLocales = scandir(__DIR__ . '/../localization');
foreach ($prefLocales as $locale => $q) foreach ($browserPreferedLocales as $locale => $q)
{ {
if (in_array($locale, $availableLocales)) if (in_array($locale, $availableLocales))
{ {
@ -66,6 +72,7 @@ class LocaleMiddleware extends BaseMiddleware
} }
} }
// Falback to default locale
return GROCY_DEFAULT_LOCALE; return GROCY_DEFAULT_LOCALE;
} }
} }

View File

@ -12,8 +12,6 @@ class DatabaseMigrationService extends BaseService
public function MigrateDatabase() public function MigrateDatabase()
{ {
define('GROCY_DATABASE_MIGRATIONS_RUNNING', true);
DatabaseService::GetInstance()->ExecuteDbStatement("CREATE TABLE IF NOT EXISTS migrations (migration INTEGER NOT NULL PRIMARY KEY UNIQUE, execution_time_timestamp DATETIME DEFAULT (datetime('now', 'localtime')))"); DatabaseService::GetInstance()->ExecuteDbStatement("CREATE TABLE IF NOT EXISTS migrations (migration INTEGER NOT NULL PRIMARY KEY UNIQUE, execution_time_timestamp DATETIME DEFAULT (datetime('now', 'localtime')))");
$migrationFiles = []; $migrationFiles = [];

View File

@ -8,12 +8,12 @@ use Gettext\Translator;
class LocalizationService extends BaseService class LocalizationService extends BaseService
{ {
public function __construct(string $culture) public function __construct(string $locale)
{ {
parent::__construct(); parent::__construct();
$this->Culture = $culture; $this->Locale = $locale;
$this->LoadLocalizations($culture); $this->LoadLocalizations($locale);
} }
protected $Po; protected $Po;
@ -22,8 +22,8 @@ class LocalizationService extends BaseService
protected $PotMain; protected $PotMain;
protected $Translator; protected $Translator;
protected $TranslatorQu; protected $TranslatorQu;
protected $Culture; protected $Locale;
private static $instanceMap = []; private static $InstanceMap = [];
public function CheckAndAddMissingTranslationToPot($text) public function CheckAndAddMissingTranslationToPot($text)
{ {
@ -112,31 +112,24 @@ class LocalizationService extends BaseService
} }
} }
public static function GetInstance(string $culture = '') public static function GetInstance(string $locale = '')
{ {
if (empty($culture)) if (empty($locale))
{ {
if (defined('GROCY_LOCALE')) $locale = GROCY_LOCALE;
{
$culture = GROCY_LOCALE;
}
else
{
$culture = GROCY_DEFAULT_LOCALE;
}
} }
if (!in_array($culture, self::$instanceMap)) if (!in_array($locale, self::$InstanceMap))
{ {
self::$instanceMap[$culture] = new self($culture); self::$InstanceMap[$locale] = new self($locale);
} }
return self::$instanceMap[$culture]; return self::$InstanceMap[$locale];
} }
private function LoadLocalizations() private function LoadLocalizations()
{ {
$culture = $this->Culture; $locale = $this->Locale;
if (GROCY_MODE === 'dev') if (GROCY_MODE === 'dev')
{ {
@ -153,47 +146,46 @@ class LocalizationService extends BaseService
$this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/demo_data.pot')); $this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/demo_data.pot'));
} }
$this->Po = Translations::fromPoFile(__DIR__ . "/../localization/$culture/strings.po"); $this->Po = Translations::fromPoFile(__DIR__ . "/../localization/$locale/strings.po");
if (file_exists(__DIR__ . "/../localization/$culture/chore_assignment_types.po")) if (file_exists(__DIR__ . "/../localization/$locale/chore_assignment_types.po"))
{ {
$this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/chore_assignment_types.po")); $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$locale/chore_assignment_types.po"));
} }
if (file_exists(__DIR__ . "/../localization/$culture/component_translations.po")) if (file_exists(__DIR__ . "/../localization/$locale/component_translations.po"))
{ {
$this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/component_translations.po")); $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$locale/component_translations.po"));
} }
if (file_exists(__DIR__ . "/../localization/$culture/stock_transaction_types.po")) if (file_exists(__DIR__ . "/../localization/$locale/stock_transaction_types.po"))
{ {
$this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/stock_transaction_types.po")); $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$locale/stock_transaction_types.po"));
} }
if (file_exists(__DIR__ . "/../localization/$culture/chore_period_types.po")) if (file_exists(__DIR__ . "/../localization/$locale/chore_period_types.po"))
{ {
$this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/chore_period_types.po")); $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$locale/chore_period_types.po"));
} }
if (file_exists(__DIR__ . "/../localization/$culture/userfield_types.po")) if (file_exists(__DIR__ . "/../localization/$locale/userfield_types.po"))
{ {
$this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/userfield_types.po")); $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$locale/userfield_types.po"));
} }
if (file_exists(__DIR__ . "/../localization/$culture/permissions.po")) if (file_exists(__DIR__ . "/../localization/$locale/permissions.po"))
{ {
$this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/permissions.po")); $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$locale/permissions.po"));
} }
if (file_exists(__DIR__ . "/../localization/$culture/locales.po")) if (file_exists(__DIR__ . "/../localization/$locale/locales.po"))
{ {
$this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/locales.po")); $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$locale/locales.po"));
} }
// Load demo data localizations also during database migrations since e.g. default quantity units are created localized by that if (GROCY_MODE !== 'production' && file_exists(__DIR__ . "/../localization/$locale/demo_data.po"))
if ((GROCY_MODE !== 'production' || defined('GROCY_DATABASE_MIGRATIONS_RUNNING')) && file_exists(__DIR__ . "/../localization/$culture/demo_data.po"))
{ {
$this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/demo_data.po")); $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$locale/demo_data.po"));
} }
$this->Translator = new Translator(); $this->Translator = new Translator();