diff --git a/config/areas/languages/views.php b/config/areas/languages/views.php index f37ded0247..e31ba86aa0 100644 --- a/config/areas/languages/views.php +++ b/config/areas/languages/views.php @@ -15,8 +15,8 @@ $language = Find::language($code); $link = '/languages/' . $language->code(); $strings = []; - $foundation = App::instance()->defaultLanguage()->translations(); - $translations = $language->translations(); + $foundation = App::instance()->defaultLanguage()->translations()->toArray(); + $translations = $language->translations()->toArray(); ksort($foundation); diff --git a/src/Cms/AppTranslations.php b/src/Cms/AppTranslations.php index 5efc39d754..84b5706577 100644 --- a/src/Cms/AppTranslations.php +++ b/src/Cms/AppTranslations.php @@ -32,7 +32,7 @@ protected function i18n(): void $this->multilang() === true && $language = $this->languages()->find($locale) ) { - $data = array_merge($data, $language->translations()); + $data = array_merge($data, $language->translations()->toArray()); } @@ -163,7 +163,7 @@ public function translation(string|null $locale = null): Translation // inject current language translations if ($language = $this->language($locale)) { - $inject = array_merge($inject, $language->translations()); + $inject = array_merge($inject, $language->translations()->toArray()); } // load from disk instead @@ -185,7 +185,7 @@ public function translations(): Translations if ($languages = $this->languages()) { foreach ($languages as $language) { $languageCode = $language->code(); - $languageTranslations = $language->translations(); + $languageTranslations = $language->translations()->toArray(); // merges language translations with extensions translations if (empty($languageTranslations) === false) { diff --git a/src/Cms/Core.php b/src/Cms/Core.php index 11ca635c16..30f2f5389d 100644 --- a/src/Cms/Core.php +++ b/src/Cms/Core.php @@ -316,33 +316,34 @@ 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', + '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', + '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', + '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', + 'translations' => null, ]; } diff --git a/src/Cms/Language.php b/src/Cms/Language.php index 592186722a..12b682df46 100644 --- a/src/Cms/Language.php +++ b/src/Cms/Language.php @@ -43,7 +43,7 @@ class Language protected string $name; protected array $slugs; protected array $smartypants; - protected array $translations; + protected LanguageTranslations $translations; protected string|null $url; /** @@ -62,7 +62,6 @@ public function __construct(array $props) $this->name = trim($props['name'] ?? $this->code); $this->slugs = $props['slugs'] ?? []; $this->smartypants = $props['smartypants'] ?? []; - $this->translations = $props['translations'] ?? []; $this->url = $props['url'] ?? null; if ($locale = $props['locale'] ?? null) { @@ -70,6 +69,8 @@ public function __construct(array $props) } else { $this->locale = [LC_ALL => $this->code]; } + + $this->translations = new LanguageTranslations($this, $props['translations'] ?? []); } /** @@ -124,7 +125,7 @@ public function clone(array $props = []): static 'name' => $this->name, 'slugs' => $this->slugs, 'smartypants' => $this->smartypants, - 'translations' => $this->translations, + 'translations' => $this->translations->toArray(), 'url' => $this->url, ], $props)); } @@ -409,6 +410,7 @@ public function save(): static { try { $existingData = Data::read($this->root()); + $data['translations'] = $this->translations()->load($existingData['translations'] ?? []); } catch (Throwable) { $existingData = []; } @@ -419,7 +421,7 @@ public function save(): static 'direction' => $this->direction(), 'locale' => Locale::export($this->locale()), 'name' => $this->name(), - 'translations' => $this->translations(), + 'translations' => $this->translations()->toArray(), 'url' => $this->url, ]; @@ -427,6 +429,13 @@ public function save(): static ksort($data); + // save translations to the custom root and remove translations + // to prevent duplication write into the language file + if ($this->translations()->root() !== null) { + $this->translations()->save($data['translations'] ?? []); + $data['translations'] = []; + } + Data::write($this->root(), $data); return $this; @@ -474,9 +483,9 @@ public function toArray(): array } /** - * Returns the translation strings for this language + * Returns the language translations object for this language */ - public function translations(): array + public function translations(): LanguageTranslations { return $this->translations; } @@ -507,7 +516,7 @@ public function update(array $props = null): static $updated = $this->clone($props); if (isset($props['translations']) === true) { - $updated->translations = $props['translations']; + $updated->translations = new LanguageTranslations($updated, $props['translations']); } // validate the updated language diff --git a/src/Cms/LanguageTranslations.php b/src/Cms/LanguageTranslations.php new file mode 100644 index 0000000000..622f60bc2c --- /dev/null +++ b/src/Cms/LanguageTranslations.php @@ -0,0 +1,141 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class LanguageTranslations +{ + protected App $kirby; + protected array $data; + + public function __construct( + protected Language $language, + self|array $translations = [] + ) { + $this->kirby = App::instance(); + $this->setTranslations($translations); + } + + /** + * Returns a single translation string by key + */ + public function get(string $key, string $default = null): string|null + { + return $this->data[$key] ?? $default; + } + + /** + * Loads the language translations based on custom roots for provided language code + */ + public function load(array $default = []): array + { + if ($file = static::root()) { + try { + return Data::read($file); + } catch (Exception) { + return $default; + } + } + + return $default; + } + + /** + * Saves the language translations in the custom root + * @internal + * + * @return $this + */ + public function save(array $translations = []): static + { + if ($root = $this->root()) { + $this->data = $translations; + Data::write($root, $translations); + } + + return $this; + } + + /** + * Returns custom translations root path if defined + */ + public function root(): string|null + { + $kirby = App::instance(); + $root = $kirby->root('translations'); + $file = $root . '/' . $this->language->code() . '.php'; + + if ( + $root !== null && + F::exists($file) === true + ) { + return $file; + } + + return null; + } + + /** + * Removes a translation key + * + * @return $this + */ + public function remove(string $key): static + { + unset($this->data[$key]); + return $this; + } + + /** + * Sets the translation key + * + * @return $this + */ + public function set(string $key, string|null $value = null): static + { + $this->data[$key] = $value; + return $this; + } + + /** + * Set translations + * + * @return $this + */ + public function setTranslations(self|array $translations = []): static + { + if (empty($translations) === false) { + if ($translations instanceof self) { + $this->data = $translations->toArray(); + } else { + $this->data = $translations; + } + } else { + $this->data = static::load(); + } + + return $this; + } + + /** + * Returns translations + */ + public function toArray(): array + { + return $this->data; + } +} diff --git a/src/Cms/LanguageVariable.php b/src/Cms/LanguageVariable.php index b26336e95b..64e3bbe022 100644 --- a/src/Cms/LanguageVariable.php +++ b/src/Cms/LanguageVariable.php @@ -46,7 +46,7 @@ public static function create( $kirby = App::instance(); $language = $kirby->defaultLanguage(); - $translations = $language->translations(); + $translations = $language->translations()->toArray(); if ($kirby->translation()->get($key) !== null) { if (isset($translations[$key]) === true) { @@ -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]); + $translations = $language->translations(); + $translations->remove($this->key); + $language->update(['translations' => $translations]); } 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->translations()->get($this->key) !== null; } /** @@ -105,8 +103,7 @@ public function key(): string public function update(string $value): static { $translations = $this->language->translations(); - $translations[$this->key] = $value; - + $translations->set($this->key, $value); $language = $this->language->update(['translations' => $translations]); return $language->variable($this->key); @@ -117,6 +114,6 @@ public function update(string $value): static */ public function value(): string|null { - return $this->language->translations()[$this->key] ?? null; + return $this->language->translations()->get($this->key); } }