├── .editorconfig ├── .gitignore ├── .prettierrc ├── LICENSE.md ├── README.md ├── composer.json ├── config ├── buttons.php ├── dialogs.php └── translations.php ├── index.css ├── index.js ├── index.php ├── src └── LanguageSelector.php └── translations ├── de.yml ├── en.yml ├── fr.yml ├── is.yml └── pt_PT.yml /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md,*.txt] 13 | trim_trailing_whitespace = false 14 | insert_final_newline = false 15 | 16 | [*.php] 17 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS files 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "singleQuote": true, 6 | "trailingComma": "none", 7 | "bracketSpacing": true, 8 | "semi": true, 9 | "proseWrap": "preserve", 10 | "arrowParens": "avoid", 11 | "htmlWhitespaceSensitivity": "css" 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 JUNO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kirby Language Selector 2 | 3 | This plugin for **Kirby 5** replaces the default language dropdown with a customized version. It displays the translation state of each language and adds a dropdown for deleting translations. 4 | 5 | ![Language Selector in Kirby panel](https://github.com/junohamburg/kirby-language-selector/assets/77532479/5beffbc7-22d5-42b8-8026-8e379db6b99f) 6 | 7 | Please note: If you are using **Kirby 4**, please install [v1.1.9](https://github.com/junohamburg/kirby-language-selector/releases/tag/1.1.9). 8 | 9 | ### UI states 10 | 11 | ![Language Selector states](https://github.com/junohamburg/kirby-language-selector/assets/77532479/8ecb96a9-c406-4664-99c9-44ceb628dfa7)
12 | Left to right: No translations, some translations, dropdown. 13 | 14 | ## Installation 15 | 16 | ### Download 17 | 18 | Download and copy this repository to `/site/plugins/kirby-language-selector`. 19 | 20 | ### Composer 21 | 22 | ``` 23 | composer require junohamburg/kirby-language-selector 24 | ``` 25 | 26 | ### Git submodule 27 | 28 | ``` 29 | git submodule add https://github.com/junohamburg/kirby-language-selector.git site/plugins/kirby-language-selector 30 | ``` 31 | 32 | ## Setup 33 | 34 | Install the plugin in a multi-language Kirby site. 35 | 36 | Please note: On small viewports, the default language dropdown is displayed. 37 | 38 | ## Available options 39 | 40 | **site/config/config.php** 41 | 42 | ```php 43 | [ 47 | 'allowDelete' => false, // Hide dropdown for deleting translations, default: true 48 | ] 49 | ]; 50 | ``` 51 | 52 | ## Contributions 53 | ### Plugin Translations 54 | 55 | The dialog text and tooltips are not translated into every language that the Kirby panel supports. For missing languages, feel free to add a pull request with a new `yml` translation file in [this folder](https://github.com/junohamburg/kirby-language-selector/tree/main/translations). 56 | 57 | ## Disclaimer 58 | 59 | This plugin is provided "as is" with no guarantee. Use it at your own risk and always test it yourself before using it in a production environment. If you find any issues, please [create a new issue](https://github.com/junohamburg/kirby-language-selector/issues/new). 60 | 61 | ## Similar plugins 62 | 63 | [https://github.com/Daandelange/k3-translations](https://github.com/Daandelange/k3-translations)
64 | [https://github.com/doldenroller/k3-translation-status](https://github.com/doldenroller/k3-translation-status)
65 | [https://github.com/sietseveenman/kirby3-language-sync](https://github.com/sietseveenman/kirby3-language-sync)
66 | 67 | ## License 68 | 69 | MIT 70 | 71 | ## Credits 72 | 73 | - [JUNO](https://juno-hamburg.com) 74 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "junohamburg/kirby-language-selector", 3 | "description": "Kirby Language Selector", 4 | "license": "MIT", 5 | "type": "kirby-plugin", 6 | "version": "2.0.2", 7 | "authors": [ 8 | { 9 | "name": "JUNO", 10 | "email": "github@juno-hamburg.com" 11 | } 12 | ], 13 | "require": { 14 | "getkirby/composer-installer": "^1.2" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /config/buttons.php: -------------------------------------------------------------------------------- 1 | fn (ModelWithContent $model) => new LanguageSelector($model), 7 | ]; -------------------------------------------------------------------------------- /config/dialogs.php: -------------------------------------------------------------------------------- 1 | language($language); 17 | 18 | return [ 19 | 'component' => 'k-remove-dialog', 20 | 'props' => [ 21 | 'text' => I18n::template( 22 | 'junohamburg.language-selector.' . $model::CLASS_ALIAS . '.delete.confirm', 23 | [ 24 | 'language' => Escape::html($language->name()), 25 | 'title' => match (true) { 26 | $model instanceof Site => $model->title(), 27 | $model instanceof Page => $model->title(), 28 | $model instanceof File => $model->filename(), 29 | $model instanceof User => $model->name() ?? $model->email(), 30 | default => throw new Exception('Invalid model type'), 31 | }, 32 | ] 33 | ) 34 | ] 35 | ]; 36 | }; 37 | 38 | $submit = function (ModelWithContent $model, string $language) { 39 | $model->version('latest')->delete($language); 40 | 41 | return [ 42 | 'redirect' => $model->panel()->url(true) 43 | ]; 44 | }; 45 | 46 | // Dialog routes 47 | return [ 48 | 'page.translation.delete' => $forModel = [ 49 | 'pattern' => '(pages/.*?)/translation/(:any)', 50 | 'load' => fn (string $model, string $language) => 51 | $load(model: Find::parent($model), language: $language), 52 | 'submit' => fn (string $model, string $language) => 53 | $submit(model: Find::parent($model), language: $language) 54 | ], 55 | 'page.file.translation.delete' => $forFile = [ 56 | 'pattern' => '(pages/.*?)/files/(:any)/translation/(:any)', 57 | 'load' => fn (string $model, string $filename, string $language) => 58 | $load(model: Find::file($model, $filename), language: $language), 59 | 'submit' => fn (string $model, string $filename, string $language) => 60 | $submit(model: Find::file($model, $filename), language: $language) 61 | ], 62 | 'site.translation.delete' => [ 63 | ...$forModel, 64 | 'pattern' => '(site)/translation/(:any)' 65 | ], 66 | 'site.file.translation.delete' => [ 67 | ...$forFile, 68 | 'pattern' => '(site)/files/(:any)/translation/(:any)' 69 | ], 70 | 'user.translation.delete' => [ 71 | ...$forModel, 72 | 'pattern' => '(users/.*?)/translation/(:any)' 73 | ], 74 | 'user.file.translation.delete' => [ 75 | ...$forFile, 76 | 'pattern' => '(users/.*?)/files/(:any)/translation/(:any)' 77 | ], 78 | 'account.translation.delete' => [ 79 | ...$forModel, 80 | 'pattern' => '(account)/translation/(:any)' 81 | ], 82 | 'account.file.translation.delete' => [ 83 | ...$forFile, 84 | 'pattern' => '(account)/files/(:any)/translation/(:any)' 85 | ] 86 | ]; -------------------------------------------------------------------------------- /config/translations.php: -------------------------------------------------------------------------------- 1 | 'junohamburg.language-selector.' . $key 21 | ), 22 | $translation 23 | ); 24 | } 25 | 26 | return $translations; -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | @container (max-width: 30rem) { 2 | .k-button-group.k-language-selector { 3 | display: none; 4 | } 5 | } 6 | 7 | @container (min-width: 30rem) { 8 | .k-language-selector + .k-languages-dropdown { 9 | display: none; 10 | } 11 | } 12 | 13 | /* Language Selector style */ 14 | .k-language-selector > .k-button, 15 | .k-language-selector + .k-languages-dropdown > .k-button { 16 | text-transform: uppercase; 17 | } 18 | 19 | /* Same width buttons */ 20 | .k-language-selector > .k-button:not([data-dropdown='true']) { 21 | --button-padding: 0; 22 | min-width: 2rem; 23 | } 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | panel.plugin('junohamburg/language-selector', { 2 | components: { 3 | 'k-language-selector': { 4 | inheritAttrs: false, 5 | props: { 6 | dropdown: String, 7 | language: Object, 8 | languages: { 9 | type: Array, 10 | default: () => [] 11 | }, 12 | options: { 13 | type: Array, 14 | default: () => [] 15 | } 16 | }, 17 | methods: { 18 | change(language) { 19 | this.$reload({ 20 | query: { 21 | language: language.code 22 | } 23 | }); 24 | }, 25 | remove(code) { 26 | this.$panel.dialog.open( 27 | this.$panel.view.path + '/translation/' + code 28 | ); 29 | } 30 | }, 31 | template: ` 32 |
33 | 38 | 48 | 58 | 64 | 65 | 66 | 74 |
75 | ` 76 | } 77 | } 78 | }); 79 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | __DIR__ . '/src/LanguageSelector.php', 13 | ]); 14 | 15 | Kirby::plugin('junohamburg/language-selector', [ 16 | 'options' => [ 17 | 'allowDelete' => true, 18 | ], 19 | 'areas' => [ 20 | 'site' => function () { 21 | if (Kirby::instance()->language() === null) { 22 | return []; 23 | } 24 | 25 | return [ 26 | 'buttons' => require __DIR__ . '/config/buttons.php', 27 | 'dialogs' => require __DIR__ . '/config/dialogs.php', 28 | ]; 29 | } 30 | ], 31 | 'translations' => require __DIR__ . '/config/translations.php', 32 | ]); 33 | -------------------------------------------------------------------------------- /src/LanguageSelector.php: -------------------------------------------------------------------------------- 1 | kirby = $model->kirby(); 16 | 17 | parent::__construct( 18 | component: 'k-language-selector', 19 | model: $model, 20 | ); 21 | } 22 | 23 | protected function languages(): array 24 | { 25 | $dropdown = new LanguagesDropdown($this->model); 26 | $options = $dropdown->options(); 27 | $options = A::filter($options, fn ($option) => $option !== '-'); 28 | $options = A::map($options, fn ($option) => [ 29 | ...$option, 30 | 'theme' => $this->theme($option), 31 | 'title' => $this->title($option), 32 | ]); 33 | return array_values($options); 34 | } 35 | 36 | public function props(): array 37 | { 38 | return [ 39 | ...parent::props(), 40 | 'dropdown' => $this->model->panel()->url(true) . '/languages', 41 | 'language' => $this->kirby->language()->toArray(), 42 | 'languages' => $this->languages(), 43 | 'options' => $this->options(), 44 | ]; 45 | } 46 | 47 | /** 48 | * Returns the options for the translations delete dropdown 49 | */ 50 | protected function options(): array 51 | { 52 | if ($this->kirby->option('junohamburg.language-selector.allowDelete') !== true) { 53 | return []; 54 | } 55 | 56 | $version = $this->model->version('latest'); 57 | 58 | // Create dropdown options 59 | $options = A::filter( 60 | $this->languages(), 61 | fn ($language) => $language['default'] === false 62 | ); 63 | 64 | $options = A::map($options, fn ($language) => [ 65 | 'click' => $language['code'], 66 | 'disabled' => $version->exists($language['code']) === false, 67 | 'icon' => 'trash', 68 | 'text' => I18n::template( 69 | 'junohamburg.language-selector.delete', 70 | ['language' => $language['text']] 71 | ), 72 | ]); 73 | 74 | // Do not show options dropdown if 75 | // there are no translations for any non-default language 76 | $enabled = A::filter( 77 | $options, 78 | fn ($language) => $language['disabled'] === false 79 | ); 80 | 81 | if (count($enabled) === 0) { 82 | return []; 83 | } 84 | 85 | return array_values($options); 86 | } 87 | 88 | /** 89 | * Returns the theme for a language button 90 | */ 91 | protected function theme(array $language): string|null 92 | { 93 | if ($language['code'] === $this->kirby->language()->code()) { 94 | return 'dark'; 95 | } 96 | 97 | if ($this->model->version('latest')->exists($language['code']) === false) { 98 | return 'empty'; 99 | } 100 | 101 | return null; 102 | } 103 | 104 | /** 105 | * Returns the title for a language button 106 | */ 107 | protected function title(array $language): string 108 | { 109 | if ($this->model->version('latest')->exists($language['code']) === false) { 110 | return I18n::template( 111 | 'junohamburg.language-selector.empty', 112 | ['language' => $language['text']] 113 | ); 114 | } 115 | 116 | return $language['text']; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /translations/de.yml: -------------------------------------------------------------------------------- 1 | title: Übersetzungen 2 | settings: Übersetzungseinstellungen 3 | empty: '{language}, nicht übersetzt' 4 | delete: Übersetzung in {language} löschen 5 | site.delete.confirm: Willst du die Übersetzung in {language} wirklich löschen? 6 | page.delete.confirm: Willst du die Übersetzung in {language} der Seite {title} wirklich löschen? 7 | file.delete.confirm: Willst du die Übersetzung in {language} der Datei {title} wirklich löschen? 8 | user.delete.confirm: Willst du die Übersetzung in {language} des Accounts {title} wirklich löschen? 9 | account.delete.confirm: Willst du die Übersetzung in {language} deines Accounts wirklich löschen? 10 | -------------------------------------------------------------------------------- /translations/en.yml: -------------------------------------------------------------------------------- 1 | title: Translations 2 | settings: Translation settings 3 | empty: '{language}, not translated' 4 | delete: Delete {language} translation 5 | site.delete.confirm: Do you really want to delete the {language} translation? 6 | page.delete.confirm: Do you really want to delete the {language} translation for the page {title}? 7 | file.delete.confirm: Do you really want to delete the {language} translation for the file {title}? 8 | user.delete.confirm: Do you really want to delete the {language} translation for the user {title}? 9 | account.delete.confirm: Do you really want to delete the {language} translation for your account? 10 | -------------------------------------------------------------------------------- /translations/fr.yml: -------------------------------------------------------------------------------- 1 | title: Traductions 2 | settings: Paramètres des traductions 3 | empty: '{language}, non traduit' 4 | delete: Supprimer la traduction en {language} 5 | site.delete.confirm: Voulez-vous vraiment supprimer la traduction en {language} ? 6 | page.delete.confirm: Voulez-vous vraiment supprimer la traduction en {language} de la page {title} ? 7 | file.delete.confirm: Voulez-vous vraiment supprimer la traduction en {language} du fichier {title} ? 8 | user.delete.confirm: Voulez-vous vraiment supprimer la traduction en {language} de l’utilisateur {title} ? 9 | account.delete.confirm: Voulez-vous vraiment supprimer la traduction en {language} de votre compte ? 10 | -------------------------------------------------------------------------------- /translations/is.yml: -------------------------------------------------------------------------------- 1 | title: Þýðingar 2 | settings: Stillingar þýðingar 3 | empty: '{language}, ekki þýtt' 4 | delete: Eyða {language} þýðingu 5 | site.delete.confirm: Ertu nú alveg viss um að þú viljir eyða {language} þýðingunni? 6 | page.delete.confirm: Ertu nú alveg viss um að þú viljir eyða {language} þýðingunni fyrir {title} síðuna? 7 | file.delete.confirm: Ertu nú alveg viss um að þú viljir eyða the {language} þýðingunni fyrir {title} skránna? 8 | user.delete.confirm: Ertu nú alveg viss um að þú viljir eyða {language} þýðingunni fyrir {title} notandann? 9 | account.delete.confirm: Ertu nú alveg viss um að þú viljir eyða {language} þýðingunni sem tilheyrir þínum notanda? 10 | -------------------------------------------------------------------------------- /translations/pt_PT.yml: -------------------------------------------------------------------------------- 1 | title: Traduções 2 | settings: Configurações de tradução 3 | empty: '{language}, não traduzido' 4 | delete: Apagar a tradução em {language} 5 | site.delete.confirm: Tem a certeza que pretende apagar a tradução em {language}? 6 | page.delete.confirm: Tem a certeza que pretende apagar a tradução em {language} para a página {title}? 7 | file.delete.confirm: Tem a certeza que pretende apagar a tradução em {language} para o ficheiro {title}? 8 | user.delete.confirm: Tem a certeza que pretende apagar a tradução em {language} para o utilizador {title}? 9 | account.delete.confirm: Tem a certeza que pretende apagar a tradução em {language} para a sua conta? 10 | --------------------------------------------------------------------------------