impr: Migliorata i18n

Spostata funzione di traduzione JS all'interno di `utils.js` e poi portata in una variabile globale.

Le traduzioni sono ora salvate nella cache.
This commit is contained in:
Maicol Battistini 2021-11-09 10:14:08 +01:00
parent 2e18a96e5e
commit 0fd3c9a313
No known key found for this signature in database
GPG Key ID: 4FDB0F87CDB1D34A
12 changed files with 109 additions and 106 deletions

View File

@ -1,3 +1,5 @@
root: true
extends:
- '@openstamanager'
globals:
__: true

View File

@ -4,7 +4,6 @@ namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Inertia\Middleware;
use Nette\Utils\Json;
class HandleInertiaRequests extends Middleware
{
@ -17,41 +16,24 @@ class HandleInertiaRequests extends Middleware
*/
protected $rootView = 'app';
/**
* Determines the current asset version.
*
* @see https://inertiajs.com/asset-versioning
*/
public function version(Request $request): string|null
{
return parent::version($request);
}
/**
* Defines the props that are shared by default.
*
* @see https://inertiajs.com/shared-data
*/
public function share(Request $request): array
final public function share(Request $request): array
{
return array_merge(parent::share($request), [
'locale' => fn () => app()->getLocale(),
'translations' => function () {
$json = resource_path('lang/'.app()->getLocale().'.json');
if (!is_file($json)) {
return [];
}
return Json::decode(file_get_contents($json));
},
]);
}
public function rootView(Request $request): string
final public function rootView(Request $request): string
{
if (in_array($request->route()?->uri(), ['setup', 'login'], true)) {
return 'external';
}
return $this->rootView;
}
}

View File

@ -3,6 +3,7 @@
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Nette\Utils\Json;
class AppServiceProvider extends ServiceProvider
{
@ -19,6 +20,11 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot(): void
{
//
cache()->rememberForever(
'translations_' . app()->getLocale(),
fn () => Json::encode(
Json::decode(file_get_contents(resource_path('lang/'.app()->getLocale().'.json')))
)
);
}
}

View File

@ -1,7 +1,3 @@
import {Vnode} from 'mithril';
import {sync as render} from 'mithril-node-render';
import {containsHTML} from '../utils';
import Component from './Component.jsx';
/**
@ -14,48 +10,8 @@ export default class Page extends Component {
component: string,
locale: string,
props: {...},
translations: {...},
url: string,
version: string,
...
} = JSON.parse($('#app').attr('data-page'));
/**
* Ritorna una traduzione
*
* @param {string|Vnode} key Stringa di cui prelevare la traduzione
* @param {Object|boolean} replace Eventuali parametri da rimpiazzare.
* Se il parametro è "true" (valore booleano), verrà ritornato il valore come stringa
* (stesso funzionamento del parametro dedicato (sotto ))
* @param {boolean} returnAsString Se impostato a "true" vien ritornata una stringa invece di
* un Vnode di Mithril
*
* @returns {Vnode}
*
* @protected
*/
__(
key: string | Vnode,
replace: {...} | boolean = {},
returnAsString: boolean = false
): Vnode {
let translation = (this.page.translations && this.page.translations[key])
? this.page.translations[key] : key;
// Ritorna la traduzione come stringa (senza sostituzione di parametri)
if ((typeof replace === 'boolean' && replace) || (replace.length === 0 && !containsHTML(translation))) {
return translation;
}
for (const k of Object.keys(replace)) {
// `'attrs' in replace[k]` controlla se replace[k] è un vnode di Mithril
translation = translation.replace(`:${k}`, ((typeof replace[k] === 'object' && 'attrs' in replace[k]) ? render(replace[k]) : replace[k]));
}
if (returnAsString || !containsHTML(translation)) {
return translation;
}
return window.m.trust(translation);
}
}

View File

@ -145,7 +145,7 @@ export default class RecordsPage extends Page {
<TableRow>
<TableCell colspan={collect(this.columns)
.count()} style="text-align: center;">
{this.__('Non sono presenti dati')}
{__('Non sono presenti dati')}
</TableCell>
</TableRow>);
}
@ -192,7 +192,7 @@ export default class RecordsPage extends Page {
this.rows.forget(instance.id);
m.redraw();
await showSnackbar(this.__('Record eliminato!'), 4000);
await showSnackbar(__('Record eliminato!'), 4000);
});
loading.hide();
@ -208,7 +208,7 @@ export default class RecordsPage extends Page {
recordDialog() {
return (
<mwc-dialog id="add-record-dialog" class="record-dialog"
heading={this.__('Aggiungi nuovo record')}
heading={__('Aggiungi nuovo record')}
style={`--mdc-dialog-max-width: ${this.recordDialogMaxWidth}`}>
<form method="PUT">
<text-field id="id" name="id" style="display: none;" data-default-value=""/>
@ -243,11 +243,11 @@ export default class RecordsPage extends Page {
})()}
</form>
<LoadingButton type="submit" slot="primaryAction" label={this.__('Conferma')}/>
<LoadingButton type="submit" slot="primaryAction" label={__('Conferma')}/>
<mwc-button slot="secondaryAction" dialogAction="cancel">
{this.__('Annulla')}
{__('Annulla')}
</mwc-button>
<mwc-button id="delete-button" slot="secondaryAction" label={this.__('Elimina')}
<mwc-button id="delete-button" slot="secondaryAction" label={__('Elimina')}
style="--mdc-theme-primary: var(--mdc-theme-error, red); float: left; display: none;">
<Mdi icon="delete-outline" slot="icon"/>
</mwc-button>
@ -258,9 +258,9 @@ export default class RecordsPage extends Page {
deleteRecordDialog(): Children {
return (
<mwc-dialog id="confirm-delete-record-dialog">
<p>{this.__('Sei sicuro di voler eliminare questo record?')}</p>
<LoadingButton id="confirm-button" slot="primaryAction" label={this.__('Sì')}/>
<mwc-button slot="secondaryAction" dialogAction="discard" label={this.__('No')}/>
<p>{__('Sei sicuro di voler eliminare questo record?')}</p>
<LoadingButton id="confirm-button" slot="primaryAction" label={__('Sì')}/>
<mwc-button slot="secondaryAction" dialogAction="discard" label={__('No')}/>
</mwc-dialog>
);
}
@ -280,7 +280,7 @@ export default class RecordsPage extends Page {
</TableBody>
</DataTable>
<mwc-fab id="add-record" label={this.__('Aggiungi')} class="sticky">
<mwc-fab id="add-record" label={__('Aggiungi')} class="sticky">
<Mdi icon="plus" slot="icon"/>
</mwc-fab>
{this.recordDialog()}
@ -342,11 +342,11 @@ export default class RecordsPage extends Page {
this.rows.put(model.id, model);
m.redraw();
await showSnackbar(this.__('Record salvato'), 4000);
await showSnackbar(__('Record salvato'), 4000);
}
} else {
loading.hide();
await showSnackbar(this.__('Campi non validi. Controlla i dati inseriti'));
await showSnackbar(__('Campi non validi. Controlla i dati inseriti'));
}
});
}

View File

@ -33,7 +33,6 @@ export function extend(proto, methods, callback) {
for (const method of allMethods) {
const original = proto[method];
// eslint-disable-next-line no-param-reassign
proto[method] = function (...arguments_) {
const value = original ? original.apply(this, arguments_) : undefined;
@ -79,7 +78,6 @@ export function override(object, methods, newMethod) {
for (const method of allMethods) {
const original = object[method];
// eslint-disable-next-line no-param-reassign
object[method] = function (...arguments_) {
return Reflect.apply(newMethod, this, [original.bind(this), ...arguments_]);
};

View File

@ -51,74 +51,74 @@ export default class SetupPage extends Page {
view(vnode) {
const examplesTexts = collect();
for (const example of ['localhost', 'root', 'mysql', 'openstamanager']) {
examplesTexts.put(example, this.__('Esempio: :example', {example}));
examplesTexts.put(example, __('Esempio: :example', {example}));
}
return (
<>
<Card outlined className="center" style="width: 85%;">
<Content>
<img src={logoUrl} className="center" alt={this.__('OpenSTAManager')} />
<img src={logoUrl} className="center" alt={__('OpenSTAManager')} />
<LayoutGrid>
<Row>
<Cell columnspan-desktop="8">
<h2>{this.__('Benvenuto in :name!', {name: <strong>{this.__('OpenSTAManager')}</strong>})}</h2>
<p>{this.__('Puoi procedere alla configurazione tecnica del software attraverso i '
<h2>{__('Benvenuto in :name!', {name: <strong>{__('OpenSTAManager')}</strong>})}</h2>
<p>{__('Puoi procedere alla configurazione tecnica del software attraverso i '
+ 'parametri seguenti, che potranno essere corretti secondo necessità tramite il file .env.')}<br/>
{this.__("Se necessiti supporto puoi contattarci tramite l':contactLink o tramite il nostro :forumLink.", {
{__("Se necessiti supporto puoi contattarci tramite l':contactLink o tramite il nostro :forumLink.", {
// eslint-disable-next-line no-secrets/no-secrets
contactLink: <a href="https://www.openstamanager.com/contattaci/?subject=Assistenza%20installazione%20OSM">{this.__('assistenza ufficiale')}</a>,
forumLink: <a href="https://forum.openstamanager.com">{this.__('forum')}</a>
contactLink: <a href="https://www.openstamanager.com/contattaci/?subject=Assistenza%20installazione%20OSM">{__('assistenza ufficiale')}</a>,
forumLink: <a href="https://forum.openstamanager.com">{__('forum')}</a>
})}</p>
<h4>{this.__('Formato date')}</h4>
<h4>{__('Formato date')}</h4>
<small>
{this.__('I formati sono impostabili attraverso lo standard previsto da :link.',
{__('I formati sono impostabili attraverso lo standard previsto da :link.',
{link: <a href="https://www.php.net/manual/en/function.date.php#refsect1-function.date-parameters">PHP</a>})
}
</small>
<Row style="margin-top: 8px;">
<Cell>
<text-field name="timestamp_format" label={this.__('Formato data lunga')}
<text-field name="timestamp_format" label={__('Formato data lunga')}
required value="d/m/Y H:i">
<Mdi icon="calendar-clock" slot="icon"/>
</text-field>
</Cell>
<Cell>
<text-field name="date_format" label={this.__('Formato data corta')}
<text-field name="date_format" label={__('Formato data corta')}
required value="d/m/Y">
<Mdi icon="calendar-month-outline" slot="icon"/>
</text-field>
</Cell>
<Cell>
<text-field name="time_format" label={this.__('Formato orario')} required
<text-field name="time_format" label={__('Formato orario')} required
value="H:i">
<Mdi icon="clock-outline" slot="icon"/>
</text-field>
</Cell>
</Row>
<hr/>
<h4>{this.__('Database')}</h4>
<h4>{__('Database')}</h4>
<Row>
<Cell columnspan="4">
<text-field name="host" label={this.__('Host')} required
<text-field name="host" label={__('Host')} required
helper={examplesTexts.get('localhost')}>
<Mdi icon="server-network" slot="icon"/>
</text-field>
</Cell>
<Cell columnspan="4">
<text-field name="username" label={this.__('Nome utente')} required
<text-field name="username" label={__('Nome utente')} required
helper={examplesTexts.get('root')}>
<Mdi icon="account-outline" slot="icon"/>
</text-field>
</Cell>
<Cell columnspan="4">
<text-field name="password" label={this.__('Password')} required
<text-field name="password" label={__('Password')} required
helper={examplesTexts.get('mysql')}>
<Mdi icon="lock-outline" slot="icon"/>
</text-field>
</Cell>
<Cell columnspan="4">
<text-field name="database_name" label={this.__('Nome database')} required
<text-field name="database_name" label={__('Nome database')} required
helper={examplesTexts.get('openstamanager')}>
<Mdi icon="database-outline" slot="icon"/>
</text-field>
@ -127,38 +127,38 @@ export default class SetupPage extends Page {
<hr/>
<Row>
<Cell>
<small>{this.__('* Campi obbligatori')}</small>
<small>{__('* Campi obbligatori')}</small>
</Cell>
<Cell>
<mwc-button raised label={this.__('Salva e installa')}>
<mwc-button raised label={__('Salva e installa')}>
<Mdi icon="check" slot="icon"/>
</mwc-button>
</Cell>
<Cell>
<mwc-button outlined label={this.__('Testa il database')}>
<mwc-button outlined label={__('Testa il database')}>
<Mdi icon="test-tube" slot="icon"/>
</mwc-button>
</Cell>
</Row>
</Cell>
<Cell>
<h4>{this.__('Lingua')}</h4>
<h4>{__('Lingua')}</h4>
<mwc-select>
{this.languages()}
</mwc-select>
<hr />
<h4>{this.__('Licenza')}</h4>
<p>{this.__('OpenSTAManager è tutelato dalla licenza GPL 3.0, da accettare obbligatoriamente per poter utilizzare il gestionale.')}</p>
<h4>{__('Licenza')}</h4>
<p>{__('OpenSTAManager è tutelato dalla licenza GPL 3.0, da accettare obbligatoriamente per poter utilizzare il gestionale.')}</p>
<mwc-textarea value={this.page.props.license} rows="15" cols="40" disabled />
<Row style="margin-top: 5px;">
<Cell columnspan-desktop="8" columnspan-tablet="8">
<mwc-formfield label={this.__('Ho visionato e accetto la licenza')}>
<mwc-formfield label={__('Ho visionato e accetto la licenza')}>
<mwc-checkbox name="license_agreement"/>
</mwc-formfield>
</Cell>
<Cell>
<a href="https://www.gnu.org/licenses/translations.en.html#GPL" target="_blank">
<mwc-button label={this.__('Versioni tradotte')}>
<mwc-button label={__('Versioni tradotte')}>
<Mdi icon="license" slot="icon"/>
</mwc-button>
</a>
@ -170,7 +170,7 @@ export default class SetupPage extends Page {
</Content>
</Card>
<mwc-fab id="contrast-switcher" className="sticky contrast-light"
label={this.__('Attiva/disattiva contrasto elevato')}>
label={__('Attiva/disattiva contrasto elevato')}>
<Mdi icon="contrast-circle" slot="icon" className="light-bg"/>
</mwc-fab>
</>

3
resources/js/app.js vendored
View File

@ -11,12 +11,15 @@ import {
import $ from 'cash-dom';
import m from 'mithril';
import {__} from './utils';
// Fix Mithril JSX durante la compilazione
m.Fragment = '[';
// Variabili globali
window.$ = $;
window.m = m;
window.__ = __;
InertiaProgress.init();

44
resources/js/utils.js vendored
View File

@ -1,6 +1,8 @@
// noinspection JSUnusedGlobalSymbols
import type {Cash} from 'cash-dom/dist/cash';
import {type Vnode} from 'mithril';
import {sync as render} from 'mithril-node-render';
/**
* Check if class/object A is the same as or a subclass of class B.
@ -72,3 +74,45 @@ export function isFormValid(element: Cash | HTMLFontElement) {
return isValid;
}
/**
* Ritorna una traduzione
*
* @param {string|Vnode} key Stringa di cui prelevare la traduzione
* @param {Object|boolean} replace Eventuali parametri da rimpiazzare.
* Se il parametro è "true" (valore booleano), verrà ritornato il valore come stringa
* (stesso funzionamento del parametro dedicato (sotto ))
* @param {boolean} returnAsString Se impostato a "true" vien ritornata una stringa invece di
* un Vnode di Mithril
*
* @returns {Vnode}
*
* @protected
*/
export function __(
key: string | Vnode,
replace: { [string]: string | Vnode | any } | boolean = {},
returnAsString: boolean = false
): Vnode | string {
let translation = key;
// noinspection JSUnresolvedVariable
if (window.translations && window.translations[key]) {
translation = window.translations[key];
}
// Returns translation as string (no parameters replacement)
if ((typeof replace === 'boolean' && replace) || (replace.length === 0 && !containsHTML(translation))) {
return translation;
}
for (const k of Object.keys(replace)) {
// `'attrs' in replace[k]` checks if `replace[k]` is a Mithril Vnode
translation = translation.replace(`:${k}`, ((typeof replace[k] === 'object' && 'attrs' in replace[k]) ? render(replace[k]) : replace[k]));
}
if (returnAsString || !containsHTML(translation)) {
return translation;
}
return window.m.trust(translation);
}

View File

@ -69,5 +69,8 @@
@client
@vite('app')
@include('layouts.translations')
</body>
</html>

View File

@ -8,5 +8,7 @@
@client
@vite('app')
@include('layouts.translations')
</body>
</html>

View File

@ -0,0 +1,7 @@
@php
/** @var string $translations */
$translations = cache('translations_' . app()->getLocale());
@endphp
<script>
window.translations = JSON.parse('{!! $translations !!}')
</script>