mirror of
https://github.com/grocy/grocy.git
synced 2026-04-06 21:06:15 +02:00
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:
parent
64b8195f62
commit
7f598058bf
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,6 +1,7 @@
|
||||||
/public/node_modules
|
/public/node_modules
|
||||||
node_modules
|
node_modules
|
||||||
.yarn
|
.yarn
|
||||||
|
.vagrant
|
||||||
/vendor
|
/vendor
|
||||||
/.release
|
/.release
|
||||||
embedded.txt
|
embedded.txt
|
||||||
|
|
|
||||||
8
Vagrantfile
vendored
Normal file
8
Vagrantfile
vendored
Normal 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
|
||||||
30
buildfiles/generate-locales.php
Normal file
30
buildfiles/generate-locales.php
Normal 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
22
buildfiles/grocy.nginx
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
buildfiles/provision-vagrant.sh
Normal file
14
buildfiles/provision-vagrant.sh
Normal 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
|
||||||
|
|
@ -130,7 +130,14 @@ class BaseController
|
||||||
$this->View->set('__n', function ($number, $singularForm, $pluralForm) use ($localizationService) {
|
$this->View->set('__n', function ($number, $singularForm, $pluralForm) use ($localizationService) {
|
||||||
return $localizationService->__n($number, $singularForm, $pluralForm);
|
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?)
|
// TODO: Better handle this generically based on the current language (header in .po file?)
|
||||||
$dir = 'ltr';
|
$dir = 'ltr';
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,15 @@ function clean(cb)
|
||||||
function build(cb)
|
function build(cb)
|
||||||
{
|
{
|
||||||
// body omitted
|
// 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)
|
function publish(cb)
|
||||||
|
|
@ -229,6 +237,11 @@ function resourceFileCopy(cb)
|
||||||
)();
|
)();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function makeLocales()
|
||||||
|
{
|
||||||
|
return subprocess.exec("php buildfiles/generate-locales.php");
|
||||||
|
}
|
||||||
|
|
||||||
function copyLocales(cb)
|
function copyLocales(cb)
|
||||||
{
|
{
|
||||||
return parallel(
|
return parallel(
|
||||||
|
|
@ -290,4 +303,5 @@ function bundle(cb)
|
||||||
.pipe(dest('.release'))
|
.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 }
|
||||||
15
js/grocy.js
15
js/grocy.js
|
|
@ -44,12 +44,25 @@ class GrocyClass
|
||||||
this.EditObjectProduct = config.EditObjectProduct;
|
this.EditObjectProduct = config.EditObjectProduct;
|
||||||
this.RecipePictureFileName = config.RecipePictureFileName;
|
this.RecipePictureFileName = config.RecipePictureFileName;
|
||||||
this.InstructionManualFileNameName = config.InstructionManualFileNameName;
|
this.InstructionManualFileNameName = config.InstructionManualFileNameName;
|
||||||
|
this.Locale = config.Locale;
|
||||||
|
|
||||||
this.Components = {};
|
this.Components = {};
|
||||||
|
|
||||||
// Init some classes
|
// Init some classes
|
||||||
this.Api = new GrocyApi(this);
|
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.FrontendHelpers = new GrocyFrontendHelpers(this, this.Api);
|
||||||
this.WakeLock = new WakeLock(this);
|
this.WakeLock = new WakeLock(this);
|
||||||
this.UISound = new UISound(this);
|
this.UISound = new UISound(this);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,28 @@ class GrocyApi
|
||||||
this.Grocy = Grocy;
|
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)
|
Get(apiFunction, success, error)
|
||||||
{
|
{
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ use Gettext\Translator;
|
||||||
|
|
||||||
class LocalizationService
|
class LocalizationService
|
||||||
{
|
{
|
||||||
|
const DOMAIN = 'grocy/userstrings';
|
||||||
|
|
||||||
protected $Po;
|
protected $Po;
|
||||||
|
|
||||||
protected $PoUserStrings;
|
protected $PoUserStrings;
|
||||||
|
|
@ -62,11 +64,15 @@ class LocalizationService
|
||||||
return $this->Po->toJsonString();
|
return $this->Po->toJsonString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(string $culture)
|
public function __construct(string $culture, $deferLoading = false)
|
||||||
{
|
{
|
||||||
$this->Culture = $culture;
|
$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)
|
public function __n($number, $singularForm, $pluralForm)
|
||||||
|
|
@ -110,7 +116,7 @@ class LocalizationService
|
||||||
return $this->getDatabaseService()->GetDbConnection();
|
return $this->getDatabaseService()->GetDbConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function LoadLocalizations()
|
public function LoadLocalizations($includeDynamic = true)
|
||||||
{
|
{
|
||||||
$culture = $this->Culture;
|
$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");
|
$this->Po = Translations::fromPoFile(__DIR__ . "/../localization/$culture/strings.po");
|
||||||
|
|
||||||
if (file_exists(__DIR__ . "/../localization/$culture/chore_assignment_types.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"));
|
$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;
|
$quantityUnits = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$quantityUnits = $this->getDatabase()->quantity_units()->fetchAll();
|
$quantityUnits = $this->getDatabase()->quantity_units()->fetchAll();
|
||||||
|
|
@ -200,10 +214,13 @@ class LocalizationService
|
||||||
$this->PoUserStrings[] = $translation;
|
$this->PoUserStrings[] = $translation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($this->Po == null)
|
||||||
|
{
|
||||||
|
$this->Po = new Translations();
|
||||||
|
$this->Po->setDomain(self::DOMAIN);
|
||||||
|
}
|
||||||
|
|
||||||
$this->Po = $this->Po->mergeWith($this->PoUserStrings);
|
$this->Po = $this->Po->mergeWith($this->PoUserStrings);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->Translator = new Translator();
|
|
||||||
$this->Translator->loadTranslations($this->Po);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@
|
||||||
Currency:'{{ GROCY_CURRENCY }}',
|
Currency:'{{ GROCY_CURRENCY }}',
|
||||||
CalendarFirstDayOfWeek: '{{ GROCY_CALENDAR_FIRST_DAY_OF_WEEK }}',
|
CalendarFirstDayOfWeek: '{{ GROCY_CALENDAR_FIRST_DAY_OF_WEEK }}',
|
||||||
CalendarShowWeekNumbers: {{ BoolToString(GROCY_CALENDAR_SHOW_WEEK_OF_YEAR) }},
|
CalendarShowWeekNumbers: {{ BoolToString(GROCY_CALENDAR_SHOW_WEEK_OF_YEAR) }},
|
||||||
|
Locale: '{{ $GrocyLocale }}',
|
||||||
GettextPo: {!! $GettextPo !!},
|
GettextPo: {!! $GettextPo !!},
|
||||||
FeatureFlags: {!! json_encode($featureFlags) !!},
|
FeatureFlags: {!! json_encode($featureFlags) !!},
|
||||||
Webhooks: {
|
Webhooks: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user