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.
This commit is contained in:
Katharina Bogad 2021-06-20 23:12:17 +02:00
parent 64b8195f62
commit 7f598058bf
11 changed files with 162 additions and 13 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
/public/node_modules
node_modules
.yarn
.vagrant
/vendor
/.release
embedded.txt

8
Vagrantfile vendored Normal file
View File

@ -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

View File

@ -0,0 +1,30 @@
<?php
use Grocy\Services\LocalizationService;
/* This file statically generates json to handle
* frontend translations.
*/
define("GROCY_DATAPATH", __DIR__ . '/../data');
// Load composer dependencies
require_once __DIR__ . '/../vendor/autoload.php';
// Load config files
require_once GROCY_DATAPATH . '/config.php';
require_once __DIR__ . '/../config-dist.php'; // For not in own config defined values we use the default ones
echo "Searching for localizations in " . __DIR__ . '/../localization/* \n';
$translations = array_filter(glob(__DIR__ . '/../localization/*'), 'is_dir');
// ensure the target directory is there
if(!is_dir(__DIR__ . '/../public/js/locales/grocy/')) {
mkdir(__DIR__ . '/../public/js/locales/grocy/', 0777, true);
}
foreach($translations as $lang) {
$culture = basename($lang);
echo "Generating " . $culture . "...\n";
$ls = LocalizationService::getInstance($culture, true);
$ls->LoadLocalizations(false);
file_put_contents(__DIR__ .'/../public/js/locales/grocy/'.$culture.'.json', $ls->GetPoAsJsonString());
}

22
buildfiles/grocy.nginx Normal file
View File

@ -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;
}
}

View File

@ -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

View File

@ -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';

View File

@ -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 }
export { build, js, vendor, viewjs, css, live, clean, resourceFileCopy, copyLocales, publish, release, bundle, makeLocales }

View File

@ -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);

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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: {