diff --git a/config/areas/languages/views.php b/config/areas/languages/views.php index 2dcdcd84a3..b46d058b0e 100644 --- a/config/areas/languages/views.php +++ b/config/areas/languages/views.php @@ -17,8 +17,8 @@ $language = Find::language($code); $link = '/languages/' . $language->code(); $strings = []; - $foundation = $kirby->defaultLanguage()->translations(); - $translations = $language->translations(); + $foundation = $kirby->defaultLanguage()->variables()->toArray(); + $variables = $language->variables()->toArray(); // TODO: update following line and adapt for update and // delete options when `languageVariables.*` permissions available @@ -29,7 +29,7 @@ foreach ($foundation as $key => $value) { $strings[] = [ 'key' => $key, - 'value' => $translations[$key] ?? null, + 'value' => $variables[$key] ?? null, 'options' => [ [ 'click' => 'update', diff --git a/src/Cms/AppTranslations.php b/src/Cms/AppTranslations.php index 896c0e7498..b2e3fd3293 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() + ]; } @@ -135,7 +138,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 @@ -160,14 +166,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 b14dc6d2ce..7446710a67 100644 --- a/src/Cms/Core.php +++ b/src/Cms/Core.php @@ -331,33 +331,33 @@ public function load(): Loader public function roots(): array { return $this->cache['roots'] ??= [ - 'kirby' => fn (array $roots) => dirname(__DIR__, 2), - 'i18n' => fn (array $roots) => $roots['kirby'] . '/i18n', - 'i18n:translations' => fn (array $roots) => $roots['i18n'] . '/translations', - 'i18n:rules' => fn (array $roots) => $roots['i18n'] . '/rules', - - 'index' => fn (array $roots) => static::$indexRoot ?? dirname(__DIR__, 3), - 'assets' => fn (array $roots) => $roots['index'] . '/assets', - 'content' => fn (array $roots) => $roots['index'] . '/content', - 'media' => fn (array $roots) => $roots['index'] . '/media', - 'panel' => fn (array $roots) => $roots['kirby'] . '/panel', - 'site' => fn (array $roots) => $roots['index'] . '/site', - 'accounts' => fn (array $roots) => $roots['site'] . '/accounts', - 'blueprints' => fn (array $roots) => $roots['site'] . '/blueprints', - 'cache' => fn (array $roots) => $roots['site'] . '/cache', - 'collections' => fn (array $roots) => $roots['site'] . '/collections', - 'commands' => fn (array $roots) => $roots['site'] . '/commands', - 'config' => fn (array $roots) => $roots['site'] . '/config', - 'controllers' => fn (array $roots) => $roots['site'] . '/controllers', - 'languages' => fn (array $roots) => $roots['site'] . '/languages', - 'license' => fn (array $roots) => $roots['config'] . '/.license', - 'logs' => fn (array $roots) => $roots['site'] . '/logs', - 'models' => fn (array $roots) => $roots['site'] . '/models', - 'plugins' => fn (array $roots) => $roots['site'] . '/plugins', - 'sessions' => fn (array $roots) => $roots['site'] . '/sessions', - 'snippets' => fn (array $roots) => $roots['site'] . '/snippets', - 'templates' => fn (array $roots) => $roots['site'] . '/templates', - 'roles' => fn (array $roots) => $roots['blueprints'] . '/users', + 'kirby' => fn(array $roots) => dirname(__DIR__, 2), + 'i18n' => fn(array $roots) => $roots['kirby'] . '/i18n', + 'i18n:translations' => fn(array $roots) => $roots['i18n'] . '/translations', + 'i18n:rules' => fn(array $roots) => $roots['i18n'] . '/rules', + 'index' => fn(array $roots) => static::$indexRoot ?? dirname(__DIR__, 3), + 'assets' => fn(array $roots) => $roots['index'] . '/assets', + 'content' => fn(array $roots) => $roots['index'] . '/content', + 'media' => fn(array $roots) => $roots['index'] . '/media', + 'panel' => fn(array $roots) => $roots['kirby'] . '/panel', + 'site' => fn(array $roots) => $roots['index'] . '/site', + 'accounts' => fn(array $roots) => $roots['site'] . '/accounts', + 'blueprints' => fn(array $roots) => $roots['site'] . '/blueprints', + 'cache' => fn(array $roots) => $roots['site'] . '/cache', + 'collections' => fn(array $roots) => $roots['site'] . '/collections', + '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', + 'license' => fn(array $roots) => $roots['config'] . '/.license', + 'logs' => fn(array $roots) => $roots['site'] . '/logs', + 'models' => fn(array $roots) => $roots['site'] . '/models', + 'plugins' => fn(array $roots) => $roots['site'] . '/plugins', + 'roles' => fn(array $roots) => $roots['blueprints'] . '/users', + 'sessions' => fn(array $roots) => $roots['site'] . '/sessions', + 'snippets' => fn(array $roots) => $roots['site'] . '/snippets', + 'templates' => fn(array $roots) => $roots['site'] . '/templates', ]; } diff --git a/src/Cms/Language.php b/src/Cms/Language.php index a8be660bdc..c4dfbb3aa6 100644 --- a/src/Cms/Language.php +++ b/src/Cms/Language.php @@ -48,8 +48,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 @@ -68,7 +68,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) { @@ -76,6 +75,8 @@ public function __construct(array $props) } else { $this->locale = [LC_ALL => $this->code]; } + + $this->variables = new LanguageVariables($this, $props['variables'] ?? $props['translations'] ?? []); } /** @@ -130,7 +131,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)); } @@ -222,6 +223,7 @@ public function delete(): bool LanguageRules::delete($this); // apply before hook + /** @var \Kirby\Cms\Language $language */ $language = $kirby->apply( 'language.delete:before', ['language' => $this], @@ -235,6 +237,9 @@ public function delete(): bool throw new Exception('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) { @@ -482,12 +487,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; @@ -548,11 +560,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(); } /** @@ -581,6 +596,7 @@ public function update(array|null $props = null): static // trigger before hook + /** @var \Kirby\Cms\Language $language */ $language = $kirby->apply( 'language.update:before', [ @@ -594,7 +610,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 @@ -644,4 +660,12 @@ public function variable(string $key, bool $decode = false): LanguageVariable return new LanguageVariable($this, $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 7ca846bf87..d0d941ed2d 100644 --- a/src/Cms/LanguageVariable.php +++ b/src/Cms/LanguageVariable.php @@ -44,21 +44,21 @@ public static function create( throw new InvalidArgumentException('The variable needs a valid key'); } - $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('The variable already exists'); } throw new DuplicateException('The variable is part of the core translation and cannot be overwritten'); } - $translations[$key] = $value ?? ''; + $variables[$key] = $value ?? ''; - $language->update(['translations' => $translations]); + $language->update(['variables' => $variables]); return $language->variable($key); } @@ -72,11 +72,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; @@ -88,7 +86,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; } /** @@ -104,19 +102,19 @@ public function key(): string */ public function update(string|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|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; + } +}