diff --git a/.devtools/transifex_download.bat b/.devtools/transifex_download.bat index ac3536ab..c6cb8f9d 100644 --- a/.devtools/transifex_download.bat +++ b/.devtools/transifex_download.bat @@ -7,4 +7,5 @@ copy /Y localization\en\component_translations.po localization\en_GB\component_t copy /Y localization\en\chore_period_types.po localization\en_GB\chore_period_types.po copy /Y localization\en\chore_assignment_types.po localization\en_GB\chore_assignment_types.po copy /Y localization\en\permissions.po localization\en_GB\permissions.po +copy /Y localization\en\locales.po localization\en_GB\locales.po popd diff --git a/.tx/config b/.tx/config index ee8b0313..ec2f9c1d 100644 --- a/.tx/config +++ b/.tx/config @@ -48,3 +48,9 @@ file_filter = localization//permissions.po source_file = localization/permissions.pot source_lang = en type = PO + +[grocy.locales] +file_filter = localization//locales.po +source_file = localization/locales.pot +source_lang = en +type = PO diff --git a/README.md b/README.md index d39eb9df..8313aae1 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ If you run grocy on Linux, there is also `update.sh` (remember to make the scrip ## Localization grocy is fully localizable - the default language is English (integrated into code), a German localization is always maintained by me. You can easily help translating grocy at https://www.transifex.com/grocy/grocy, if your language is incomplete or not available yet. -(Language can be changed in `data/config.php`, e. g. `Setting('CULTURE', 'it');`) +(Language can be changed in `data/config.php`, e. g. `Setting('DEFAULT_LOCALE', 'it');`) The [pre-release demo](https://demo-prerelease.grocy.info) is available for any translation which is at least 80 % complete and will pull the translations from Transifex 10 minutes past every hour, so you can have a kind of instant preview of your contributed translations. Thank you! diff --git a/app.php b/app.php index 67c95210..6cb9703e 100644 --- a/app.php +++ b/app.php @@ -1,5 +1,6 @@ setBasePath(GROCY_BASE_PATH); } - +if (GROCY_MODE === 'production') +{ + $app->add(new \Grocy\Middleware\LocaleMiddleware($container)); +} +else { + define('GROCY_LOCALE', GROCY_DEFAULT_LOCALE); +} +$authMiddlewareClass = GROCY_AUTH_CLASS; +$app->add(new $authMiddlewareClass($container, $app->getResponseFactory())); // Add default middleware $app->addRoutingMiddleware(); $errorMiddleware = $app->addErrorMiddleware(true, false, false); $errorMiddleware->setDefaultErrorHandler( new \Grocy\Controllers\ExceptionController($app, $container) ); -if (GROCY_MODE === 'production') -{ - $app->add(new \Grocy\Middleware\LocaleMiddleware($container)); -} -else { - define(GROCY_LOCALE, GROCY_CULTURE); -} + +$app->add(CorsMiddleware::class); $app->run(); diff --git a/config-dist.php b/config-dist.php index 44e0f8e5..dc651cc8 100644 --- a/config-dist.php +++ b/config-dist.php @@ -21,7 +21,7 @@ Setting('MODE', 'production'); # Either "en" or "de" or the directory name of # one of the other available localization folders in the "/localization" directory -Setting('CULTURE', 'en'); +Setting('DEFAULT_LOCALE', 'en'); # This is used to define the first day of a week for calendar views in the frontend, # leave empty to use the locale default diff --git a/controllers/UsersController.php b/controllers/UsersController.php index 30d3a860..e850a41a 100644 --- a/controllers/UsersController.php +++ b/controllers/UsersController.php @@ -44,4 +44,16 @@ class UsersController extends BaseController ->where('parent IS NULL')->where('user_id', $args['userId']), ]); } + + public function LocaleForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'locale', [ + 'languages' => array_filter(scandir(__DIR__.'/../localization'), function ($item){ + if($item == "." || $item == "..") + return false; + return is_dir(__DIR__.'/../localization/'.$item); + }) + + ]); + } } diff --git a/localization/en/locales.po b/localization/en/locales.po new file mode 100644 index 00000000..3143e30c --- /dev/null +++ b/localization/en/locales.po @@ -0,0 +1,79 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: http://www.transifex.com/grocy/grocy/language/en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n" +"PO-Revision-Date: 2019-05-01T17:59:17+00:00\n" +"Language: en\n" +"X-Domain: grocy/locales\n" + +msgid "cs" +msgstr "Czech" + +msgid "da" +msgstr "Danish" + +msgid "de" +msgstr "German" + +msgid "el_GR" +msgstr "Greek" + +msgid "en" +msgstr "English" + +msgid "en_GB" +msgstr "English (Great Britain)" + +msgid "es" +msgstr "Spanish" + +msgid "fr" +msgstr "French" + +msgid "hu" +msgstr "Hungarian" + +msgid "it" +msgstr "Italian" + +msgid "ja" +msgstr "Japanese" + +msgid "ko_KR" +msgstr "Korean" + +msgid "nl" +msgstr "Dutch" + +msgid "no" +msgstr "Norwegian" + +msgid "pl" +msgstr "Polish" + +msgid "pt_BR" +msgstr "Portuguese (Brazil)" + +msgid "pt_PT" +msgstr "Portuguese (Portugal)" + +msgid "ru" +msgstr "Russian" + +msgid "sk_SK" +msgstr "Slovak" + +msgid "sv_SE" +msgstr "Swedish" + +msgid "tr" +msgstr "Turkish" + +msgid "zh_TW" +msgstr "Chinese (Taiwan)" diff --git a/localization/locales.pot b/localization/locales.pot new file mode 100644 index 00000000..8d688ba2 --- /dev/null +++ b/localization/locales.pot @@ -0,0 +1,79 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: http://www.transifex.com/grocy/grocy/language/en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n" +"PO-Revision-Date: 2019-05-01T17:59:17+00:00\n" +"Language: en\n" +"X-Domain: grocy/locales\n" + +msgid "cs" +msgstr "" + +msgid "da" +msgstr "" + +msgid "de" +msgstr "" + +msgid "el_GR" +msgstr "" + +msgid "en" +msgstr "" + +msgid "en_GB" +msgstr "" + +msgid "es" +msgstr "" + +msgid "fr" +msgstr "" + +msgid "hu" +msgstr "" + +msgid "it" +msgstr "" + +msgid "ja" +msgstr "" + +msgid "ko_KR" +msgstr "" + +msgid "nl" +msgstr "" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "" + +msgid "pt_BR" +msgstr "" + +msgid "pt_PT" +msgstr "" + +msgid "ru" +msgstr "" + +msgid "sk_SK" +msgstr "" + +msgid "sv_SE" +msgstr "" + +msgid "tr" +msgstr "" + +msgid "zh_TW" +msgstr "" diff --git a/localization/strings.pot b/localization/strings.pot index 6cea03cf..22d4c73f 100644 --- a/localization/strings.pot +++ b/localization/strings.pot @@ -1891,3 +1891,6 @@ msgstr "" msgid "If you think this is a bug, please report it" msgstr "" + +msgid "Language" +msgstr "" diff --git a/middleware/CorsMiddleware.php b/middleware/CorsMiddleware.php index 14f6eb3e..1c9a77d2 100644 --- a/middleware/CorsMiddleware.php +++ b/middleware/CorsMiddleware.php @@ -11,8 +11,6 @@ class CorsMiddleware extends BaseMiddleware { public function __invoke(Request $request, RequestHandler $handler): Response { - $response = $handler->handle($request); - //$routeContext = RouteContext::fromRequest($request); //$routingResults = $routeContext->getRoutingResults(); //$methods = $routingResults->getAllowedMethods(); @@ -23,7 +21,6 @@ class CorsMiddleware extends BaseMiddleware $response = $response->withHeader('Access-Control-Allow-Origin', '*'); $response = $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); $response = $response->withHeader('Access-Control-Allow-Headers', '*'); - $response = $response->withStatus(204); return $response; } diff --git a/middleware/LocaleMiddleware.php b/middleware/LocaleMiddleware.php index fd3fe5cb..dd80f386 100644 --- a/middleware/LocaleMiddleware.php +++ b/middleware/LocaleMiddleware.php @@ -4,14 +4,13 @@ namespace Grocy\Middleware; -use Locale; +use Grocy\Services\UsersService; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\RequestHandlerInterface as RequestHandler; class LocaleMiddleware extends BaseMiddleware { - const LOCALE_COOKIE_NAME = 'LOCALE'; public function __invoke(Request $request, RequestHandler $handler): Response { @@ -22,13 +21,16 @@ class LocaleMiddleware extends BaseMiddleware protected function getLocale(Request $request) { - $cookies = $request->getCookieParams(); - if (isset($cookies[self::LOCALE_COOKIE_NAME])) { - $locale = $cookies[self::LOCALE_COOKIE_NAME]; - if (in_array($locale, scandir(__DIR__ . '/../localization'))) { - return $locale; + if(GROCY_AUTHENTICATED) + { + $locale = UsersService::getInstance()->GetUserSetting(GROCY_USER_ID, 'locale'); + if (isset($locale)) { + if (in_array($locale, scandir(__DIR__ . '/../localization'))) { + return $locale; + } } } + $langs = join(',', $request->getHeader('Accept-Language')); // src: https://gist.github.com/spolischook/0cde9c6286415cddc088 @@ -60,6 +62,6 @@ class LocaleMiddleware extends BaseMiddleware return substr($locale, 0, 2); } } - return GROCY_CULTURE; + return GROCY_DEFAULT_LOCALE; } } diff --git a/public/js/grocy.js b/public/js/grocy.js index c87d7d5a..bd99c9e5 100644 --- a/public/js/grocy.js +++ b/public/js/grocy.js @@ -670,10 +670,14 @@ $(Grocy.UserPermissions).each(function (index, item) $('.permission-'+item.permission_name).addClass('disabled').addClass('not-allowed'); } }); -Grocy.SetLanguage = function (lang) { - var expires = new Date(); - // Expires "never" (= 30 years) - expires.setDate(expires.getDate() + 365*30); - document.cookie = "LOCALE=" + lang + "; SameSite=Lax; expires="+expires.toUTCString(); - location.reload(); -} +$('a.link-return').each(function () { + var base = $(this).data('href'); + if(base.contains('?')) + { + $(this).attr('href', base + '&returnto' + encodeURIComponent(location.pathname)); + } + else{ + $(this).attr('href', base + '?returnto=' + encodeURIComponent(location.pathname)); + } + +}) diff --git a/public/viewjs/locale.js b/public/viewjs/locale.js new file mode 100644 index 00000000..975cbabd --- /dev/null +++ b/public/viewjs/locale.js @@ -0,0 +1,17 @@ +$('#locale-save').on('click', function () { + var value = $("input:radio[name ='language']:checked").val(); + var jsonData = {'value': value}; + Grocy.Api.Put('user/settings/locale', jsonData, + function(result) + { + location.pathname = GetUriParam('returnto'); + }, + function(xhr) + { + if (!xhr.statusText.isEmpty()) + { + Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) + } + } + ); +}); diff --git a/routes.php b/routes.php index 6b12055e..6a4fbba6 100644 --- a/routes.php +++ b/routes.php @@ -6,9 +6,7 @@ use Psr\Http\Message\ResponseInterface as Response; use Slim\Routing\RouteCollectorProxy; use Grocy\Middleware\JsonMiddleware; -use Grocy\Middleware\CorsMiddleware; -$authMiddlewareClass = GROCY_AUTH_CLASS; $app->group('', function(RouteCollectorProxy $group) { @@ -34,6 +32,7 @@ $app->group('', function(RouteCollectorProxy $group) $group->get('/users', '\Grocy\Controllers\UsersController:UsersList'); $group->get('/user/{userId}', '\Grocy\Controllers\UsersController:UserEditForm'); $group->get('/user/{userId}/permissions', '\Grocy\Controllers\UsersController:PermissionList'); + $group->get('/usersettings/locale', '\Grocy\Controllers\UsersController:LocaleForm'); // Stock routes if (GROCY_FEATURE_FLAG_STOCK) @@ -136,7 +135,7 @@ $app->group('', function(RouteCollectorProxy $group) $group->get('/api', '\Grocy\Controllers\OpenApiController:DocumentationUi'); $group->get('/manageapikeys', '\Grocy\Controllers\OpenApiController:ApiKeysList'); $group->get('/manageapikeys/new', '\Grocy\Controllers\OpenApiController:CreateNewApiKey'); -})->add(new $authMiddlewareClass($container, $app->getResponseFactory())); +}); $app->group('/api', function(RouteCollectorProxy $group) { @@ -259,11 +258,10 @@ $app->group('/api', function(RouteCollectorProxy $group) $group->get('/calendar/ical', '\Grocy\Controllers\CalendarApiController:Ical')->setName('calendar-ical'); $group->get('/calendar/ical/sharing-link', '\Grocy\Controllers\CalendarApiController:IcalSharingLink'); } -})->add(JsonMiddleware::class) -->add(new $authMiddlewareClass($container, $app->getResponseFactory())); +})->add(JsonMiddleware::class); // Handle CORS preflight OPTIONS requests $app->options('/api/{routes:.+}', function(Request $request, Response $response): Response { - return $response; -})->add(CorsMiddleware::class); + return $response->withStatus(204); +}); diff --git a/services/DatabaseService.php b/services/DatabaseService.php index 1770917b..81866bd6 100644 --- a/services/DatabaseService.php +++ b/services/DatabaseService.php @@ -22,7 +22,7 @@ class DatabaseService { if (GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease') { - return GROCY_DATAPATH . '/grocy_' . GROCY_CULTURE . '.db'; + return GROCY_DATAPATH . '/grocy_' . GROCY_DEFAULT_LOCALE . '.db'; } return GROCY_DATAPATH . '/grocy.db'; diff --git a/services/DemoDataGeneratorService.php b/services/DemoDataGeneratorService.php index 58eaeb7e..e74edb75 100644 --- a/services/DemoDataGeneratorService.php +++ b/services/DemoDataGeneratorService.php @@ -9,7 +9,7 @@ class DemoDataGeneratorService extends BaseService public function __construct() { parent::__construct(); - $this->LocalizationService = new LocalizationService(GROCY_CULTURE); + $this->LocalizationService = new LocalizationService(GROCY_DEFAULT_LOCALE); } protected $LocalizationService; diff --git a/services/LocalizationService.php b/services/LocalizationService.php index 35d30c23..0c409d36 100644 --- a/services/LocalizationService.php +++ b/services/LocalizationService.php @@ -61,6 +61,8 @@ class LocalizationService $this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/strings.pot')); $this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/userfield_types.pot')); $this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/permissions.pot')); + $this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/locales.pot')); + if (GROCY_MODE !== 'production') { @@ -96,6 +98,10 @@ class LocalizationService { $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/permissions.po")); } + if (file_exists(__DIR__ . "/../localization/$culture/locales.po")) + { + $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/locales.po")); + } if (GROCY_MODE !== 'production' && file_exists(__DIR__ . "/../localization/$culture/demo_data.po")) { $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/demo_data.po")); diff --git a/views/layout/default.blade.php b/views/layout/default.blade.php index 529da314..a786eef6 100644 --- a/views/layout/default.blade.php +++ b/views/layout/default.blade.php @@ -416,6 +416,9 @@ @if(GROCY_FEATURE_FLAG_TASKS)  {{ $__t('Tasks settings') }} @endif + + {{ $__t('Language') }} + @if(GROCY_SHOW_AUTH_VIEWS)  {{ $__t('Manage users') }} diff --git a/views/locale.blade.php b/views/locale.blade.php new file mode 100644 index 00000000..57fba91e --- /dev/null +++ b/views/locale.blade.php @@ -0,0 +1,30 @@ +@extends('layout.default') + +@section('title', $__t('Language')) +@section('activeNav', '') +@section('viewJsName', 'locale') + +@section('content') +
+
+

@yield('title')

+
+
+
+
+
+
    + @foreach($languages as $lang) +
  • + +
  • + @endforeach +
+ +
+
+@endsection