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

10
app.php
View File

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

View File

@ -21,6 +21,10 @@ Setting('MODE', 'production');
// The directory name of one of the available localization folders
// 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');
// 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"
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_plural "Glasses"
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"
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
{
$locale = $this->getLocale($request);
define('GROCY_LOCALE', $locale);
define('GROCY_LOCALE', $this->GetLocale($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)
{
$locale = UsersService::GetInstance()->GetUserSetting(GROCY_USER_ID, 'locale');
if (isset($locale) && !empty($locale))
{
if (in_array($locale, scandir(__DIR__ . '/../localization')))
@ -30,11 +38,9 @@ class LocaleMiddleware extends BaseMiddleware
}
}
$langs = implode(',', $request->getHeader('Accept-Language'));
// Src: https://gist.github.com/spolischook/0cde9c6286415cddc088
$prefLocales = array_reduce(
explode(',', $langs),
// Otherwise use Browser prefered locale
$browserPreferedLocales = array_reduce(
explode(',', implode(',', $request->getHeader('Accept-Language'))),
function ($res, $el)
{
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');
foreach ($prefLocales as $locale => $q)
foreach ($browserPreferedLocales as $locale => $q)
{
if (in_array($locale, $availableLocales))
{
@ -66,6 +72,7 @@ class LocaleMiddleware extends BaseMiddleware
}
}
// Falback to default locale
return GROCY_DEFAULT_LOCALE;
}
}

View File

@ -12,8 +12,6 @@ class DatabaseMigrationService extends BaseService
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')))");
$migrationFiles = [];

View File

@ -8,12 +8,12 @@ use Gettext\Translator;
class LocalizationService extends BaseService
{
public function __construct(string $culture)
public function __construct(string $locale)
{
parent::__construct();
$this->Culture = $culture;
$this->LoadLocalizations($culture);
$this->Locale = $locale;
$this->LoadLocalizations($locale);
}
protected $Po;
@ -22,8 +22,8 @@ class LocalizationService extends BaseService
protected $PotMain;
protected $Translator;
protected $TranslatorQu;
protected $Culture;
private static $instanceMap = [];
protected $Locale;
private static $InstanceMap = [];
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'))
{
$culture = GROCY_LOCALE;
}
else
{
$culture = GROCY_DEFAULT_LOCALE;
}
$locale = GROCY_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()
{
$culture = $this->Culture;
$locale = $this->Locale;
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->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' || defined('GROCY_DATABASE_MIGRATIONS_RUNNING')) && file_exists(__DIR__ . "/../localization/$culture/demo_data.po"))
if (GROCY_MODE !== 'production' && file_exists(__DIR__ . "/../localization/$locale/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();