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..21a1c5af33 100644 --- a/src/Cms/Language.php +++ b/src/Cms/Language.php @@ -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->setTranslations($props['translations'] ?? []); } /** @@ -409,6 +410,13 @@ public function save(): static { try { $existingData = Data::read($this->root()); + + // inject translations from custom root + // returns existing translations + // if custom root is not defined as fallback + $data['translations'] = $this + ->translationsObject() + ->load($existingData['translations'] ?? []); } catch (Throwable) { $existingData = []; } @@ -427,11 +435,31 @@ 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->translationsObject()->root() !== null) { + $this->translationsObject() + ->save($data['translations'] ?? []); + $data['translations'] = []; + } + Data::write($this->root(), $data); return $this; } + /** + * Sets the translations data + * + * @return $this + */ + protected function setTranslations(array $translations = []): static + { + $this->translations = (new LanguageTranslations($this))->load($translations); + + return $this; + } + /** * Private siblings collector */ @@ -475,12 +503,25 @@ public function toArray(): array /** * Returns the translation strings for this language + * @todo In v5, remove this method and + * rename `->translationsObject()` method with the `translations`. + * This will be a breaking change. */ public function translations(): array { return $this->translations; } + /** + * Returns the language translations object for this language + * @todo In v5, rename this method name as `translations` + * @internal + */ + public function translationsObject(): LanguageTranslations + { + return new LanguageTranslations($this, $this->translations); + } + /** * Returns the absolute Url for the language */ diff --git a/src/Cms/LanguageTranslations.php b/src/Cms/LanguageTranslations.php new file mode 100644 index 0000000000..e0c28c7676 --- /dev/null +++ b/src/Cms/LanguageTranslations.php @@ -0,0 +1,140 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class LanguageTranslations +{ + protected array $data; + + public function __construct( + protected Language $language, + self|array $translations = [] + ) { + $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 + { + $this->setTranslations($translations); + + if ($root = $this->root()) { + 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; + } +}