From 7f598058bf619a3189e1bca9a09e75efe3dfb8f8 Mon Sep 17 00:00:00 2001 From: Katharina Bogad Date: Sun, 20 Jun 2021 23:12:17 +0200 Subject: [PATCH] i18n: static pot to json compilation Translations aren't embedded on every page load anymore; this saves roughly 55kB on every single request, because these can now be cached. --- .gitignore | 1 + Vagrantfile | 8 ++++++++ buildfiles/generate-locales.php | 30 +++++++++++++++++++++++++++ buildfiles/grocy.nginx | 22 ++++++++++++++++++++ buildfiles/provision-vagrant.sh | 14 +++++++++++++ controllers/BaseController.php | 9 +++++++- gulpfile.babel.js | 18 ++++++++++++++-- js/grocy.js | 15 +++++++++++++- js/lib/api.js | 22 ++++++++++++++++++++ services/LocalizationService.php | 35 ++++++++++++++++++++++++-------- views/layout/default.blade.php | 1 + 11 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 Vagrantfile create mode 100644 buildfiles/generate-locales.php create mode 100644 buildfiles/grocy.nginx create mode 100644 buildfiles/provision-vagrant.sh diff --git a/.gitignore b/.gitignore index 2d1bb9d5..3ee1f867 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /public/node_modules node_modules .yarn +.vagrant /vendor /.release embedded.txt diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 00000000..4aa371a5 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,8 @@ +Vagrant.configure("2") do |config| + config.vm.box = "debian/buster64" + + config.vm.provision "shell", path: "buildfiles/provision-vagrant.sh" + config.vm.network "forwarded_port", guest: 80, host: 8000 + config.vm.synced_folder ".", "/vagrant", disabled: true + config.vm.synced_folder ".", "/grocy", automount: true, owner: "www-data", group: "www-data" +end diff --git a/buildfiles/generate-locales.php b/buildfiles/generate-locales.php new file mode 100644 index 00000000..9bbb3073 --- /dev/null +++ b/buildfiles/generate-locales.php @@ -0,0 +1,30 @@ +LoadLocalizations(false); + file_put_contents(__DIR__ .'/../public/js/locales/grocy/'.$culture.'.json', $ls->GetPoAsJsonString()); +} \ No newline at end of file diff --git a/buildfiles/grocy.nginx b/buildfiles/grocy.nginx new file mode 100644 index 00000000..03fc9578 --- /dev/null +++ b/buildfiles/grocy.nginx @@ -0,0 +1,22 @@ +server { + listen 80 default_server; + listen [::]:80 default_server; + + root /grocy/public; + index index.php; + + server_name _; + + location / { + try_files $uri $uri/ /index.php$is_args$query_string; + } + + location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:/run/php/php7.4-fpm.sock; + } + + location ~ /\.ht { + deny all; + } +} \ No newline at end of file diff --git a/buildfiles/provision-vagrant.sh b/buildfiles/provision-vagrant.sh new file mode 100644 index 00000000..6e7b54ba --- /dev/null +++ b/buildfiles/provision-vagrant.sh @@ -0,0 +1,14 @@ +apt update + +# install php +apt -y install lsb-release apt-transport-https ca-certificates +wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg +echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/php.list +apt update + +apt -y install php7.4-fpm php7.4-cli php7.4-{bcmath,bz2,intl,gd,mbstring,zip,sqlite} nginx + +cp /grocy/buildfiles/grocy.nginx /etc/nginx/sites-enabled/default + +systemctl enable nginx.service +systemctl restart nginx.service \ No newline at end of file diff --git a/controllers/BaseController.php b/controllers/BaseController.php index 0a773501..d3054056 100644 --- a/controllers/BaseController.php +++ b/controllers/BaseController.php @@ -130,7 +130,14 @@ class BaseController $this->View->set('__n', function ($number, $singularForm, $pluralForm) use ($localizationService) { return $localizationService->__n($number, $singularForm, $pluralForm); }); - $this->View->set('GettextPo', $localizationService->GetPoAsJsonString()); + $this->View->set('GrocyLocale', $localizationService->Culture); + + // only inclue strings loaded from the database on every request, + // so that the rest can be cached and generated on build-time. + // TODO: figure out why this is needed in the first place. + $dynamicLocalizationService = new LocalizationService($localizationService->Culture, true); + $dynamicLocalizationService->LoadDynamicLocalizations(); + $this->View->set('GettextPo', $dynamicLocalizationService->GetPoAsJsonString()); // TODO: Better handle this generically based on the current language (header in .po file?) $dir = 'ltr'; diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 415b70b3..4bb5b4b0 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -152,7 +152,15 @@ function clean(cb) function build(cb) { // body omitted - return parallel(js, css, vendor, viewjs, resourceFileCopy, copyLocales, done => { done(); cb(); })(); + return parallel( + js, + css, + vendor, + viewjs, + resourceFileCopy, + copyLocales, + makeLocales, + done => { done(); cb(); })(); } function publish(cb) @@ -229,6 +237,11 @@ function resourceFileCopy(cb) )(); } +async function makeLocales() +{ + return subprocess.exec("php buildfiles/generate-locales.php"); +} + function copyLocales(cb) { return parallel( @@ -290,4 +303,5 @@ function bundle(cb) .pipe(dest('.release')) } -export { build, js, vendor, viewjs, css, live, clean, resourceFileCopy, copyLocales, publish, release, bundle } \ No newline at end of file + +export { build, js, vendor, viewjs, css, live, clean, resourceFileCopy, copyLocales, publish, release, bundle, makeLocales } \ No newline at end of file diff --git a/js/grocy.js b/js/grocy.js index 107b3680..216220bc 100644 --- a/js/grocy.js +++ b/js/grocy.js @@ -44,12 +44,25 @@ class GrocyClass this.EditObjectProduct = config.EditObjectProduct; this.RecipePictureFileName = config.RecipePictureFileName; this.InstructionManualFileNameName = config.InstructionManualFileNameName; + this.Locale = config.Locale; this.Components = {}; // Init some classes this.Api = new GrocyApi(this); - this.Translator = new Translator(config.GettextPo); + + // Merge dynamic and static locales + var strings = this.Api.LoadLanguageSync(this.Locale); + if (strings == null) + { + console.error("Could not load locale " + this.Locale + ", fallback to en."); + strings = this.Api.LoadLanguageSync("en"); + } + Object.assign(strings.messages[""], config.GettextPo.messages[""]); + this.Translator = new Translator(strings); + + + this.FrontendHelpers = new GrocyFrontendHelpers(this, this.Api); this.WakeLock = new WakeLock(this); this.UISound = new UISound(this); diff --git a/js/lib/api.js b/js/lib/api.js index 47adf15c..becaade0 100644 --- a/js/lib/api.js +++ b/js/lib/api.js @@ -6,6 +6,28 @@ class GrocyApi this.Grocy = Grocy; } + // This throws a deprecation warning in the console. + // The "clean" solution would be to move all translations + // To be promise-based async stuff, but Grocy not in a shape + // to make this easily possible right now. + // The introduction of a frontend framework like react or vue + // will make this obsolete as well. + LoadLanguageSync(locale) + { + var xmlhttp = new XMLHttpRequest(); + xmlhttp.open("GET", this.Grocy.FormatUrl('/js/locales/grocy/' + locale + '.json'), false); + xmlhttp.send(); + // if Status OK or NOT MODIFIED + if ((xmlhttp.status == 200 || xmlhttp.status == 304) && xmlhttp.readyState == 4) + { + return JSON.parse(xmlhttp.responseText); + } + else + { + return null; + } + } + Get(apiFunction, success, error) { var xhr = new XMLHttpRequest(); diff --git a/services/LocalizationService.php b/services/LocalizationService.php index 1bf1d274..6606efa4 100644 --- a/services/LocalizationService.php +++ b/services/LocalizationService.php @@ -8,6 +8,8 @@ use Gettext\Translator; class LocalizationService { + const DOMAIN = 'grocy/userstrings'; + protected $Po; protected $PoUserStrings; @@ -62,11 +64,15 @@ class LocalizationService return $this->Po->toJsonString(); } - public function __construct(string $culture) + public function __construct(string $culture, $deferLoading = false) { $this->Culture = $culture; + $this->PoUserStrings = new Translations(); + $this->PoUserStrings->setDomain(self::DOMAIN); - $this->LoadLocalizations($culture); + if(!$deferLoading) { + $this->LoadLocalizations($culture); + } } public function __n($number, $singularForm, $pluralForm) @@ -110,7 +116,7 @@ class LocalizationService return $this->getDatabaseService()->GetDbConnection(); } - private function LoadLocalizations() + public function LoadLocalizations($includeDynamic = true) { $culture = $this->Culture; @@ -133,9 +139,6 @@ class LocalizationService } } - $this->PoUserStrings = new Translations(); - $this->PoUserStrings->setDomain('grocy/userstrings'); - $this->Po = Translations::fromPoFile(__DIR__ . "/../localization/$culture/strings.po"); if (file_exists(__DIR__ . "/../localization/$culture/chore_assignment_types.po")) @@ -178,7 +181,18 @@ class LocalizationService $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/demo_data.po")); } + if($includeDynamic) + { + $this->LoadDynamicLocalizations(); + } + + $this->Translator = new Translator(); + $this->Translator->loadTranslations($this->Po); + } + + public function LoadDynamicLocalizations() { $quantityUnits = null; + try { $quantityUnits = $this->getDatabase()->quantity_units()->fetchAll(); @@ -200,10 +214,13 @@ class LocalizationService $this->PoUserStrings[] = $translation; } + if($this->Po == null) + { + $this->Po = new Translations(); + $this->Po->setDomain(self::DOMAIN); + } + $this->Po = $this->Po->mergeWith($this->PoUserStrings); } - - $this->Translator = new Translator(); - $this->Translator->loadTranslations($this->Po); } } diff --git a/views/layout/default.blade.php b/views/layout/default.blade.php index c0fd2536..a81cd939 100644 --- a/views/layout/default.blade.php +++ b/views/layout/default.blade.php @@ -68,6 +68,7 @@ Currency:'{{ GROCY_CURRENCY }}', CalendarFirstDayOfWeek: '{{ GROCY_CALENDAR_FIRST_DAY_OF_WEEK }}', CalendarShowWeekNumbers: {{ BoolToString(GROCY_CALENDAR_SHOW_WEEK_OF_YEAR) }}, + Locale: '{{ $GrocyLocale }}', GettextPo: {!! $GettextPo !!}, FeatureFlags: {!! json_encode($featureFlags) !!}, Webhooks: {