diff --git a/src/Cms/AppTranslations.php b/src/Cms/AppTranslations.php index c7bdd6a8d1..e5e74a20fa 100644 --- a/src/Cms/AppTranslations.php +++ b/src/Cms/AppTranslations.php @@ -31,7 +31,10 @@ protected function i18n(): void $this->multilang() === true && $language = $this->languages()->find($locale) ) { - $data = [...$data, ...$language->translations()]; + $data = [ + ...$data, + ...$language->variables()->toArray() + ]; } @@ -139,7 +142,10 @@ public function translation(string|null $locale = null): Translation // inject current language translations if ($language = $this->language($locale)) { - $inject = [...$inject, ...$language->translations()]; + $inject = [ + ...$inject, + ...$language->variables()->toArray() + ]; } // load from disk instead @@ -164,14 +170,14 @@ public function translations(): Translations // injects languages translations if ($languages = $this->languages()) { foreach ($languages as $language) { - $languageCode = $language->code(); - $languageTranslations = $language->translations(); + $languageCode = $language->code(); + $languageVariables = $language->variables()->toArray(); // merges language translations with extensions translations - if (empty($languageTranslations) === false) { + if (empty($languageVariables) === false) { $translations[$languageCode] = [ ...$translations[$languageCode] ?? [], - ...$languageTranslations + ...$languageVariables ]; } } diff --git a/src/Cms/Core.php b/src/Cms/Core.php index fa3544ed46..31151faa38 100644 --- a/src/Cms/Core.php +++ b/src/Cms/Core.php @@ -357,6 +357,7 @@ public function roots(): array 'commands' => fn (array $roots) => $roots['site'] . '/commands', 'config' => fn (array $roots) => $roots['site'] . '/config', 'controllers' => fn (array $roots) => $roots['site'] . '/controllers', + 'language:variables' => null, 'languages' => fn (array $roots) => $roots['site'] . '/languages', 'licenses' => fn (array $roots) => $roots['site'] . '/licenses', 'license' => fn (array $roots) => $roots['config'] . '/.license', diff --git a/src/Cms/Language.php b/src/Cms/Language.php index fde1f5d782..7e0259eeaf 100644 --- a/src/Cms/Language.php +++ b/src/Cms/Language.php @@ -10,6 +10,7 @@ use Kirby\Toolkit\Locale; use Kirby\Toolkit\Str; use Stringable; +use Throwable; /** * The `$language` object represents @@ -51,8 +52,8 @@ class Language implements Stringable protected bool $single; protected array $slugs; protected array $smartypants; - protected array $translations; protected string|null $url; + protected LanguageVariables $variables; /** * Creates a new language object @@ -73,7 +74,6 @@ public function __construct(array $props) $this->single = $props['single'] ?? false; $this->slugs = $props['slugs'] ?? []; $this->smartypants = $props['smartypants'] ?? []; - $this->translations = $props['translations'] ?? []; $this->url = $props['url'] ?? null; if ($locale = $props['locale'] ?? null) { @@ -81,6 +81,8 @@ public function __construct(array $props) } else { $this->locale = [LC_ALL => $this->code]; } + + $this->variables = new LanguageVariables($this, $props['variables'] ?? $props['translations'] ?? []); } /** @@ -135,7 +137,7 @@ public function clone(array $props = []): static 'name' => $this->name, 'slugs' => $this->slugs, 'smartypants' => $this->smartypants, - 'translations' => $this->translations, + 'variables' => $this->variables->toArray(), 'url' => $this->url, ], $props)); } @@ -225,6 +227,7 @@ public function delete(): bool LanguageRules::delete($this); // apply before hook + /** @var \Kirby\Cms\Language $language */ $language = $kirby->apply( 'language.delete:before', ['language' => $this] @@ -237,6 +240,9 @@ public function delete(): bool throw new Exception(message: 'The language could not be deleted'); } + // delete custom translations file if defined + $language->variables()->delete(); + // if needed, convert content storage to single lang foreach ($kirby->models() as $model) { if ($language->isLast() === true) { @@ -483,7 +489,11 @@ public function rules(): array */ public function save(): static { - $existingData = Data::read($this->root(), fail: false); + try { + $existingData = Data::read($this->root(), fail: false); + } catch (Throwable) { + $existingData = []; + } $data = [ ...$existingData, @@ -492,12 +502,19 @@ public function save(): static 'direction' => $this->direction(), 'locale' => Locale::export($this->locale()), 'name' => $this->name(), - 'translations' => $this->translations(), + 'variables' => $this->variables()->toArray(), 'url' => $this->url, ]; ksort($data); + // save translations to the custom root and remove translations + // to prevent duplication write into the language file + if ($this->variables()->root() !== null) { + $this->variables()->save($data['translations'] ?? []); + $data['translations'] = []; + } + Data::write($this->root(), $data); return $this; @@ -558,11 +575,14 @@ public function toArray(): array } /** - * Returns the translation strings for this language + * Alias for Language::variables() + * + * @deprecated 5.0.0 Use `::variables()` instead + * @todo 7.0.0 Remove the method */ - public function translations(): array + public function translations(): LanguageVariables { - return $this->translations; + return $this->variables(); } /** @@ -589,6 +609,7 @@ public function update(array|null $props = null): static $props['slug'] = Str::slug($props['slug'] ?? null); // trigger before hook + /** @var \Kirby\Cms\Language $language */ $language = $kirby->apply( 'language.update:before', [ @@ -601,7 +622,7 @@ public function update(array|null $props = null): static $language = $language->clone($props); if (isset($props['translations']) === true) { - $language->translations = $props['translations']; + $language->variables = $language->variables->update($props['translations'] ?? null); } // validate the language rules after before hook was applied @@ -655,4 +676,12 @@ public function variable( key: $key ); } + + /** + * Returns the language variables object for this language + */ + public function variables(): LanguageVariables + { + return $this->variables; + } } diff --git a/src/Cms/LanguageVariable.php b/src/Cms/LanguageVariable.php index 935a59861d..12df2a7298 100644 --- a/src/Cms/LanguageVariable.php +++ b/src/Cms/LanguageVariable.php @@ -48,12 +48,12 @@ public static function create( ); } - $kirby = App::instance(); - $language = $kirby->defaultLanguage(); - $translations = $language->translations(); + $kirby = App::instance(); + $language = $kirby->defaultLanguage(); + $variables = $language->variables()->toArray(); if ($kirby->translation()->get($key) !== null) { - if (isset($translations[$key]) === true) { + if (isset($variables[$key]) === true) { throw new DuplicateException( message: 'The variable already exists' ); @@ -64,9 +64,9 @@ public static function create( ); } - $translations[$key] = $value ?? ''; + $variables[$key] = $value ?? ''; - $language = $language->update(['translations' => $translations]); + $language = $language->update(['variables' => $variables]); return $language->variable($key); } @@ -80,11 +80,9 @@ public function delete(): bool { // go through all languages and remove the variable foreach ($this->kirby->languages() as $language) { - $variables = $language->translations(); - - unset($variables[$this->key]); - - $language->update(['translations' => $variables]); + $variables = $language->variables(); + $variables->remove($this->key); + $language->update(['variables' => $variables->toArray()]); } return true; @@ -96,7 +94,7 @@ public function delete(): bool public function exists(): bool { $language = $this->kirby->defaultLanguage(); - return isset($language->translations()[$this->key]) === true; + return $language->variables()->get($this->key) !== null; } /** @@ -130,19 +128,19 @@ public function language(): Language */ public function update(string|array|null $value = null): static { - $translations = $this->language->translations(); - $translations[$this->key] = $value ?? ''; + $variables = $this->language->variables(); + $variables->set($this->key, $value); - $language = $this->language->update(['translations' => $translations]); + $language = $this->language->update(['variables' => $variables->toArray()]); return $language->variable($this->key); } /** - * Returns the value if the variable has been translated. + * Returns the value if the variable has been translated */ public function value(): string|array|null { - return $this->language->translations()[$this->key] ?? null; + return $this->language->variables()->get($this->key); } } diff --git a/src/Cms/LanguageVariables.php b/src/Cms/LanguageVariables.php new file mode 100644 index 0000000000..e214546b18 --- /dev/null +++ b/src/Cms/LanguageVariables.php @@ -0,0 +1,133 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class LanguageVariables +{ + public function __construct( + protected Language $language, + protected array $data = [] + ) { + $this->data = [...$this->load(), ...$this->data]; + } + + /** + * Deletes the current language variables file + * if custom root defined + */ + public function delete(): void + { + if ($file = $this->root()) { + if (F::remove($file) !== true) { + throw new Exception('The language variables could not be deleted'); + } + } + } + + /** + * Returns a single variable string by key + */ + public function get(string $key, string|null $default = null): string|null + { + return $this->data[$key] ?? $default; + } + + /** + * Loads the language variables based on custom roots + */ + public function load(): array + { + if ($file = static::root()) { + try { + return Data::read($file); + } catch (Throwable) { + // skip when an exception thrown + } + } + + return []; + } + + /** + * Saves the language variables in the custom root + * @return $this + * @internal + */ + public function save(array $variables = []): static + { + $this->data = $variables; + + if ($root = $this->root()) { + Data::write($root, $this->data); + } + + return $this; + } + + /** + * Returns custom variables root path if defined + */ + public function root(): string|null + { + if ($root = App::instance()->root('language:variables')) { + return $root . '/' . $this->language->code() . '.php'; + } + + return null; + } + + /** + * Removes a variable key + * + * @return $this + */ + public function remove(string $key): static + { + unset($this->data[$key]); + return $this; + } + + /** + * Sets the variable key + * + * @return $this + */ + public function set(string $key, string|null $value = null): static + { + $this->data[$key] = $value; + return $this; + } + + /** + * Returns variables + */ + public function toArray(): array + { + return $this->data; + } + + /** + * Updates the variables data + */ + public function update(array $data = []): static + { + $this->data = $data; + return $this; + } +} diff --git a/src/Panel/Controller/View/LanguageViewController.php b/src/Panel/Controller/View/LanguageViewController.php index 886074b0db..2485166a52 100644 --- a/src/Panel/Controller/View/LanguageViewController.php +++ b/src/Panel/Controller/View/LanguageViewController.php @@ -90,7 +90,7 @@ public function load(): View next: $this->next(), prev: $this->prev(), title: $this->language->name(), - translations: $this->translations(), + translations: $this->variables(), url: $this->language->url(), ); } @@ -119,11 +119,11 @@ public function prev(): array|null return null; } - public function translations(): array + public function variables(): array { $strings = []; - $foundation = $this->kirby->defaultLanguage()?->translations() ?? []; - $translations = $this->language->translations(); + $foundation = $this->kirby->defaultLanguage()->variables()->toArray(); + $variables = $this->language->variables()->toArray(); // TODO: update following line and adapt for update and // delete options when `languageVariables.*` permissions available @@ -134,7 +134,7 @@ public function translations(): array foreach (array_keys($foundation) as $key) { $strings[] = [ 'key' => $key, - 'value' => $translations[$key] ?? null, + 'value' => $variables[$key] ?? null, 'options' => [ [ 'click' => 'update',