├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── UPGRADING.md ├── art ├── banner.png └── example.png ├── composer.json ├── config └── filament-translation-manager.php ├── resources ├── lang │ ├── ar │ │ └── messages.php │ ├── cs │ │ └── messages.php │ ├── en │ │ └── messages.php │ ├── es │ │ └── messages.php │ ├── fr │ │ └── messages.php │ ├── nl │ │ └── messages.php │ └── sk │ │ └── messages.php └── views │ ├── livewire │ └── translation-edit-form.blade.php │ ├── pages │ └── translation-manager-page.blade.php │ └── widgets │ └── translation-status.blade.php └── src ├── FilamentChainedTranslationManagerPlugin.php ├── FilamentTranslationManager.php ├── FilamentTranslationManagerServiceProvider.php ├── Http └── Livewire │ └── TranslationEditForm.php ├── Pages └── TranslationManagerPage.php └── Widgets └── TranslationStatusWidget.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-filament-chained-translation-manager` will be documented in this file. 4 | 5 | ## v3.3.0 - 2025-03-04 6 | 7 | Laravel 12 support 8 | 9 | ## 3.2.6 - 2024-08-20 10 | 11 | ### What's Changed 12 | 13 | * Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/statikbe/laravel-filament-chained-translation-manager/pull/42 14 | * add czech translations by @JarkaP in https://github.com/statikbe/laravel-filament-chained-translation-manager/pull/43 15 | 16 | ### New Contributors 17 | 18 | * @JarkaP made their first contribution in https://github.com/statikbe/laravel-filament-chained-translation-manager/pull/43 19 | 20 | **Full Changelog**: https://github.com/statikbe/laravel-filament-chained-translation-manager/compare/v3.2.5...v3.2.6 21 | 22 | ## v3.2.5 - 2024-06-21 23 | 24 | - disable test widget by default. 25 | 26 | ## v3.2.4 - 2024-05-13 27 | 28 | Fix Laravel 11 upgrade: increase brick/varexporter version to fix php-parser dependency incompatibility with phpunit/phpstan in laravel 11 29 | 30 | ## v3.2.3 - 2024-03-21 31 | 32 | Fix Laravel 11 support 33 | 34 | ## v3.2.2 - 2024-03-14 35 | 36 | - Laravel 11 support 37 | 38 | ## v3.2.1 - 2024-03-14 39 | 40 | - Laravel 11 support 41 | 42 | ## v3.2.0 - 2024-02-06 43 | 44 | - Upgrade chained translator lib 45 | 46 | ## V3.1.3 - 2023-12-17 47 | 48 | ### What's Changed 49 | 50 | * Customizable icon by @ReubenSiama in https://github.com/statikbe/laravel-filament-chained-translation-manager/pull/31 51 | 52 | ### New Contributors 53 | 54 | * @ReubenSiama made their first contribution in https://github.com/statikbe/laravel-filament-chained-translation-manager/pull/31 55 | 56 | **Full Changelog**: https://github.com/statikbe/laravel-filament-chained-translation-manager/compare/v3.1.2...V3.1.3 57 | 58 | ## v3.1.2 - 2023-11-21 59 | 60 | ### What's Changed 61 | 62 | - use , instead of ; in README.md by @majdghithan in https://github.com/statikbe/laravel-filament-chained-translation-manager/pull/28 63 | - add French translations by @franz825 in https://github.com/statikbe/laravel-filament-chained-translation-manager/pull/30 64 | 65 | ### New Contributors 66 | 67 | - @majdghithan made their first contribution in https://github.com/statikbe/laravel-filament-chained-translation-manager/pull/28 68 | - @franz825 made their first contribution in https://github.com/statikbe/laravel-filament-chained-translation-manager/pull/30 69 | 70 | **Full Changelog**: https://github.com/statikbe/laravel-filament-chained-translation-manager/compare/v3.1.1...v3.1.2 71 | 72 | ## v3.1.1 - 2023-10-18 73 | 74 | - fix gate widget config 75 | - fix documentation filament v2 installation 76 | 77 | Thanks to @jordinbrouwer! 78 | 79 | ## v3.1.0 - 2023-10-17 80 | 81 | - Improve package structure for Filament v3 82 | - Cleanup configuration, note that you best upgrade the configuration file, see UPGRADING.md 83 | 84 | Thanks @jordinbrouwer! 85 | 86 | ## v3.0.4 - 2023-10-03 87 | 88 | Spanish translations, thanks to @buzkall 89 | 90 | ## v3.0.3 - 2023-10-03 91 | 92 | Fix widget removal setting. 93 | 94 | Thanks, @buzkall! 95 | 96 | ## v3.0.2 - 2023-09-18 97 | 98 | Fixes a bug with the opening and collapsing of the form to edit a translation 99 | 100 | Thanks @almontasser for the fix! 101 | 102 | ## v3.0.1 - 2023-09-14 103 | 104 | Renamed the plugin name the reflect it is the chained translation manager. 105 | Please, update your panel providers. 106 | 107 | Updated documentation for filament v3 installation in a panel provider. 108 | 109 | ## 3.0.0 - 2023-08-25 110 | 111 | Support for Filament v3. 112 | 113 | Please, use v1 for Filament 2 support. 114 | 115 | ## 1.1.0 - 2023-02-20 116 | 117 | Updated to Laravel 10 118 | 119 | ## v1.1.0 - 2023-02-20 120 | 121 | - Added support for Laravel 10 122 | - Added return types 123 | 124 | ## v1.0.1 - 2022-11-27 125 | 126 | - Arabic translations 127 | - Filament menu ordering support 128 | 129 | ## v1.0.0 - 2022-11-14 130 | 131 | First release on the Filament Plugins store. 132 | 133 | ## v0.0.5 - 2022-11-13 134 | 135 | Fix dark mode bug, thanks @yob-yob! 136 | 137 | ## v0.0.4 - 2022-10-27 138 | 139 | - Added an example image 140 | - Search on group and translation keys 141 | - Search filter now looks at translation key and group 142 | 143 | **Full Changelog**: https://github.com/statikbe/laravel-filament-chained-translation-manager/compare/0.0.3...0.0.4 144 | 145 | ## v0.0.3 - 2022-10-27 146 | 147 | - Update to newest chained-translation-manager 148 | 149 | **Full Changelog**: https://github.com/statikbe/laravel-filament-chained-translation-manager/compare/0.0.2...0.0.3 150 | 151 | ### v0.0.2 - 2022-10-24 152 | 153 | - Bugfixes before release 154 | 155 | ### v0.0.1 - 2022-10-24 156 | 157 | - Initial version. 158 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Statikbe 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Laravel Filament Chained Translation Manager](art/banner.png) 2 | 3 | # Laravel Filament Chained Translation Manager 4 | 5 | ![Packagist](https://img.shields.io/packagist/v/statikbe/laravel-filament-chained-translation-manager.svg?style=for-the-badge&logo=packagist) 6 | ![Code Style Passing](https://img.shields.io/github/actions/workflow/status/statikbe/laravel-filament-chained-translation-manager/.github/workflows/fix-php-code-style-issues.yml?branch=main&style=for-the-badge&logo=github&label=code%20style) 7 | ![Downloads](https://img.shields.io/packagist/dt/statikbe/laravel-filament-chained-translation-manager.svg?style=for-the-badge) 8 | 9 | The Laravel Filament Chained Translation Manager allows you to easily edit the translations of your current Laravel environment. 10 | This translation manager uses the [Laravel Chained Translator](https://github.com/statikbe/laravel-chained-translator), 11 | that enables you to override the default translations with translations for a specific environment, e.g. 12 | a content manager can independently edit and override the translation files on the production environment from the translations provided by the developers. 13 | 14 | Typically, at some point during the development phase, a content manager wants to translate or finetune the translation 15 | strings added by developers. This often results in merge and versioning issues, when developers and content managers are 16 | working on the translation files at the same time. 17 | 18 | The Chained Translator package allows translations created by developers to exist separately from translations edited 19 | by the content manager in separate lang directories. The library merges the translations of both language directories, 20 | where the translations of the content manager (the custom translations) override those of the developer (the default translations). 21 | Check the documentation of the [Laravel Chained Translator](https://github.com/statikbe/laravel-chained-translator) for more info. 22 | 23 | There is also a [Laravel Nova Chained Translation Manager](https://github.com/statikbe/laravel-nova-chained-translation-manager) of this package. 24 | 25 | ## Features 26 | 27 | - Save translations of the current environment to separate translation files in a separate language directory to avoid version conflicts. 28 | - Immediately save translations. 29 | - Search for translations and translation keys. 30 | - Filter translations for specific groups and languages. 31 | - Only show keys with missing translations. 32 | - Shows statistics of how many fields are completely translated. 33 | 34 | This tool does not provide features to add new translation keys, because our target users are translators and 35 | content managers, and we want to avoid that they add unnecessary translation keys. 36 | 37 | ## Installation 38 | 39 | > **Note** 40 | > For **Filament 2.x** use **[v1.x](https://github.com/statikbe/laravel-filament-chained-translation-manager/releases)** version 41 | 42 | 1. You can install the package via Composer: 43 | 44 | ```bash 45 | composer require statikbe/laravel-filament-chained-translation-manager 46 | ``` 47 | 48 | 2. Register the plugin for the Filament Panels you want: 49 | 50 | ```php 51 | public function panel(Panel $panel): Panel 52 | { 53 | return $panel 54 | ->plugins([ 55 | \Statikbe\FilamentTranslationManager\FilamentChainedTranslationManagerPlugin::make(), 56 | ]); 57 | } 58 | ``` 59 | 60 | 2. Using this package requires a Filament custom theme. If you do not have one already, you can follow the instructions [on the Filament documentation site](https://filamentphp.com/docs/3.x/panels/themes#creating-a-custom-theme) to create one. Creating a new theme simply publishes the styling for the default Filament panel, so this will not change anything if you are happy with how Filament is styled out of the box. After creating a custom theme, you will need to add the following path to the `content` array of the generated `tailwind.config.js` file for the Filament theme: 61 | 62 | ```javascript 63 | "./vendor/statikbe/laravel-filament-chained-translation-manager/**/*.blade.php", 64 | ``` 65 | 66 | 3. Now run the following command to compile the plugin styles into Filament's stylesheet: 67 | 68 | ```bash 69 | npm run build 70 | ``` 71 | 72 | 4. Publish the `config` file then setup your configuration: 73 | 74 | ```bash 75 | php artisan vendor:publish --tag="filament-translation-manager-config" 76 | ``` 77 | 78 | ## Configuration 79 | 80 | You can configure the custom language directory name and extend or finetune the service provider of the 81 | [Laravel Chained Translator](https://github.com/statikbe/laravel-chained-translator). Have a look at the configuration 82 | options of the [Laravel Chained Translator library](https://github.com/statikbe/laravel-chained-translator). 83 | 84 | ### Supported locales 85 | 86 | There are two ways to change the supported locales. 87 | 88 | #### Option 1 89 | 90 | Set up the supported locales using the configuration. By default, it will fallback to the locale and fallback locale. However, you can customize the configuration to include additional locales as follows: 91 | 92 | ```php 93 | 'locales' => [ 94 | 'en', 95 | 'fr', 96 | ], 97 | ``` 98 | 99 | #### Option 2 100 | 101 | If your application already has a config that declares your locales than you are able to set the supported locales in any service provider. 102 | Create a new one or use the `app/Providers/AppServiceProvider.php` and set the supported locales as an array in the boot function as follows: 103 | 104 | ```php 105 | use Statikbe\FilamentTranslationManager\FilamentTranslationManager; 106 | 107 | public function boot() 108 | { 109 | FilamentTranslationManager::setLocales(['en', 'nl']); 110 | } 111 | ``` 112 | 113 | ### Gate 114 | 115 | You can restrict access to the Translation Manager by configuring the Gate variable. 116 | 117 | ```php 118 | 'gate' => 'view-filament-translation-manager', 119 | ``` 120 | 121 | ### Ignoring groups 122 | 123 | You can choose to exclude specific groups of translations from appearing in Filament. Create an array with the keys that you wish to ignore: 124 | 125 | ```php 126 | 'ignore_groups' => [ 127 | 'auth', 128 | ], 129 | ``` 130 | 131 | ### Navigation Icon 132 | 133 | You can customize the navigation icon by configuring the `navigation-icon` variable 134 | ``` 135 | 'navigation_icon' => 'heroicon-o-language', 136 | ``` 137 | 138 | Or you can replace the icon with your own custom icon set, by registering a different icon for this icon: 139 | `filament-chained-translation-manager::nav-icon`. 140 | 141 | ## Usage 142 | 143 | The library creates a new directory for the new translations, see [Laravel Chained Translator](https://github.com/statikbe/laravel-chained-translator). 144 | Check the configuration options of the [Laravel Chained Translator](https://github.com/statikbe/laravel-chained-translator) package to change this. 145 | 146 | Additionally, please note that the Translation Manager is automatically included in the Filament menu. 147 | 148 | ### Merging translations 149 | 150 | You can combine the custom translations of the current environment with the default translation files, 151 | by running the command provided by the [Laravel Chained Translator](https://github.com/statikbe/laravel-chained-translator). 152 | 153 | ## Screenshots 154 | 155 | ![Example of Laravel Filament Chained Translation Manager](art/example.png) 156 | 157 | ## Upgrading 158 | 159 | Please see [UPGRADING](UPGRADING.md) for details. 160 | 161 | ## Changelog 162 | 163 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 164 | 165 | ## Contributing 166 | 167 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. 168 | 169 | ## Security Vulnerabilities 170 | 171 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 172 | 173 | ## Credits 174 | 175 | - [Kobe Christiaensen](https://github.com/Kobo-one) 176 | - [Sten Govaerts](https://github.com/sten) 177 | - [All Contributors](../../contributors) 178 | 179 | ## License 180 | 181 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 182 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | # Upgrading 2 | 3 | Because there are sometimes breaking changes, an upgrade may not always be easy. There might be edge cases that this guide does not cover. Feel free to help improve this guide. 4 | 5 | ## From 3.0 to 3.1 6 | 7 | You can perform the upgrade by renaming the configuration variables (located in config/filament-translation-manager.php): 8 | 9 | - Change `supported_locales` to `locales`. 10 | - Move `access.gate` to `gate`. 11 | 12 | There have also been some translations that have been renamed: 13 | 14 | - Change `navigation-group` to `navigation_group`. 15 | -------------------------------------------------------------------------------- /art/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/statikbe/laravel-filament-chained-translation-manager/125e5e70f72e065610da879e4ea4375de6f5c632/art/banner.png -------------------------------------------------------------------------------- /art/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/statikbe/laravel-filament-chained-translation-manager/125e5e70f72e065610da879e4ea4375de6f5c632/art/example.png -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "statikbe/laravel-filament-chained-translation-manager", 3 | "description": "A translation manager tool for Laravel Filament, that makes use of the Laravel Chained Translator.", 4 | "keywords": [ 5 | "Statikbe", 6 | "laravel", 7 | "laravel-filament-chained-translation-manager" 8 | ], 9 | "homepage": "https://github.com/statikbe/laravel-filament-chained-translation-manager", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Kobe Christiaensen", 14 | "email": "kobe@statik.be", 15 | "role": "Developer" 16 | }, 17 | { 18 | "name": "Sten Govaerts", 19 | "email": "sten@statik.be", 20 | "role": "Developer" 21 | } 22 | ], 23 | "require": { 24 | "php": "^8.1|^8.2|^8.3|^8.4", 25 | "filament/filament": "^3.0", 26 | "illuminate/contracts": "^10.0|^11.0|^12.0", 27 | "spatie/laravel-package-tools": "^1.14.0", 28 | "statikbe/laravel-chained-translator": "^2.5" 29 | }, 30 | "require-dev": { 31 | "laravel/pint": "^1.0", 32 | "nunomaduro/collision": "^7.0||^8.0", 33 | "nunomaduro/larastan": "^3.0", 34 | "orchestra/testbench": "^8.0", 35 | "pestphp/pest": "^3.0", 36 | "pestphp/pest-plugin-laravel": "^3.0", 37 | "phpstan/extension-installer": "^1.2", 38 | "phpstan/phpstan-deprecation-rules": "^2.0", 39 | "phpstan/phpstan-phpunit": "^2.0", 40 | "phpunit/phpunit": "^10.0|^11.0|^12.0" 41 | }, 42 | "autoload": { 43 | "psr-4": { 44 | "Statikbe\\FilamentTranslationManager\\": "src" 45 | } 46 | }, 47 | "scripts": { 48 | "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", 49 | "analyse": "vendor/bin/phpstan analyse", 50 | "format": "vendor/bin/pint" 51 | }, 52 | "config": { 53 | "sort-packages": true, 54 | "allow-plugins": { 55 | "pestphp/pest-plugin": true, 56 | "phpstan/extension-installer": true 57 | } 58 | }, 59 | "extra": { 60 | "laravel": { 61 | "providers": [ 62 | "Statikbe\\FilamentTranslationManager\\FilamentTranslationManagerServiceProvider" 63 | ], 64 | "aliases": { 65 | "FilamentTranslationManager": "Statikbe\\FilamentTranslationManager\\FilamentTranslationManager" 66 | } 67 | } 68 | }, 69 | "minimum-stability": "dev", 70 | "prefer-stable": true 71 | } 72 | -------------------------------------------------------------------------------- /config/filament-translation-manager.php: -------------------------------------------------------------------------------- 1 | [ 18 | // 'en', 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Gate 24 | |-------------------------------------------------------------------------- 25 | | 26 | | The page will use the provided gate to see if the user has access. 27 | | Note: you can define the gate in a service provider 28 | | (visit: https://laravel.com/docs/11.x/authorization) 29 | | 30 | */ 31 | 'gate' => null, 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Ignore Groups 36 | |-------------------------------------------------------------------------- 37 | | 38 | | You can list the translation groups that you do not want users to translate. 39 | | Note: the JSON files are grouped in 'json-file' by default. 40 | | (see: config/laravel-chained-translator.php) 41 | | 42 | */ 43 | 'ignore_groups' => [ 44 | // 'auth', 45 | ], 46 | 47 | /* 48 | |-------------------------------------------------------------------------- 49 | | Navigation Sort 50 | |-------------------------------------------------------------------------- 51 | | 52 | | You can specify the order in which navigation items are listed. 53 | | Accepts integer value according to Filament documentation. 54 | | (visit: https://filamentphp.com/docs/3.x/panels/navigation#sorting-navigation-items) 55 | | 56 | */ 57 | 'navigation_sort' => null, 58 | 59 | /* 60 | |-------------------------------------------------------------------------- 61 | | Widget 62 | |-------------------------------------------------------------------------- 63 | | 64 | | You can specify the widget settings: 65 | | - Enable the widget. 66 | | - Define the gate to see if the user has access. 67 | | - Specify the order in which the widget is listed. 68 | | 69 | */ 70 | 'widget' => [ 71 | 'enabled' => false, 72 | 'gate' => null, 73 | 'sort' => null, 74 | ], 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | Navigation Icon 79 | |-------------------------------------------------------------------------- 80 | | 81 | | You can specify the navigation icon. 82 | | (visit: https://blade-ui-kit.com/blade-icons?set=1#search) 83 | | Default: 'heroicon-o-language' 84 | | For null value, the icon will be hidden. 85 | | 86 | */ 87 | 88 | 'navigation_icon' => 'heroicon-o-language', 89 | ]; 90 | -------------------------------------------------------------------------------- /resources/lang/ar/messages.php: -------------------------------------------------------------------------------- 1 | 'مدير الترجمة', 5 | 'navigation_group' => 'الاعدادات', 6 | 'search_term_placeholder' => 'بحث عن ترجمة', 7 | 'selected_groups_placeholder' => 'اختر مجموعة', 8 | 'selected_languages_placeholder' => 'اختر لغة', 9 | 'only_show_missing_translations_lbl' => 'اظهار الترجمات المفقودة فقط', 10 | 'error_no_translations_for_filters' => 'لا توجد نتائج للبحث!!', 11 | 'error_no_translation_loaded' => 'تأكد من
lang
مجلد', 12 | 'previous_page' => 'السابق', 13 | 'next_page' => 'التالي', 14 | 'missing_translation' => 'الترجمة مفقودة', 15 | 'filter_action' => 'تصفية', 16 | 'filter_results' => 'تم تصفية :filtered من :total ترجمة.', 17 | 'filter_results_missing_translations' => ':missing لا توجد لها ترجمة (:percent%).', 18 | 'saved_translation' => 'تم حفظ الترجمة', 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/cs/messages.php: -------------------------------------------------------------------------------- 1 | 'Správce překladů', 5 | 'navigation_group' => 'Nastavení', 6 | 'search_term_placeholder' => 'Hledat překlad', 7 | 'selected_groups_placeholder' => 'Vyberte skupinu', 8 | 'selected_languages_placeholder' => 'Vyberte jazyk', 9 | 'only_show_missing_translations_lbl' => 'Zobraziť pouze chybějící překlady', 10 | 'error_no_translations_for_filters' => 'Zadaným filtrům neodpovídají žádné překlady.', 11 | 'error_no_translation_loaded' => 'Nebyly nalezeny žádné překlady. Zkontrolujte
lang
adresář', 12 | 'previous_page' => 'Předchozí', 13 | 'next_page' => 'Následující', 14 | 'missing_translation' => 'Překlad není vyplněn', 15 | 'filter_action' => 'Filtrovať', 16 | 'filter_results' => 'Vyfiltrováno :filtered z :total překladů.', 17 | 'filter_results_missing_translations' => ':missing chybějících překladů (:percent%).', 18 | 'saved_translation' => 'Překlad uložen', 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/en/messages.php: -------------------------------------------------------------------------------- 1 | 'Translation manager', 5 | 'navigation_group' => 'Settings', 6 | 'search_term_placeholder' => 'Search translation', 7 | 'selected_groups_placeholder' => 'Select group', 8 | 'selected_languages_placeholder' => 'Select language', 9 | 'only_show_missing_translations_lbl' => 'Only show missing translations', 10 | 'error_no_translations_for_filters' => 'Adjust the filters, there are no translations that match your query!', 11 | 'error_no_translation_loaded' => 'There were no translations found. Check your
lang
directory', 12 | 'previous_page' => 'Previous', 13 | 'next_page' => 'Next', 14 | 'missing_translation' => 'Translation not filled in', 15 | 'filter_action' => 'Filter', 16 | 'filter_results' => 'Filtered out :filtered of :total translations.', 17 | 'filter_results_missing_translations' => ':missing have missing translations (:percent%).', 18 | 'saved_translation' => 'Translation saved', 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/es/messages.php: -------------------------------------------------------------------------------- 1 | 'Gestor de Traducciones', 5 | 'navigation_group' => 'Configuración', 6 | 'search_term_placeholder' => 'Buscar traducción', 7 | 'selected_groups_placeholder' => 'Seleccionar grupo', 8 | 'selected_languages_placeholder' => 'Seleccionar idioma', 9 | 'only_show_missing_translations_lbl' => 'Mostrar solo traducciones faltantes', 10 | 'error_no_translations_for_filters' => 'Ajusta los filtros, no hay traducciones que coincidan con tu consulta.', 11 | 'error_no_translation_loaded' => 'No se encontraron traducciones. Verifica tu directorio
lang
', 12 | 'previous_page' => 'Anterior', 13 | 'next_page' => 'Siguiente', 14 | 'missing_translation' => 'Traducción no completada', 15 | 'filter_action' => 'Filtrar', 16 | 'filter_results' => 'Se filtraron :filtered de :total traducciones.', 17 | 'filter_results_missing_translations' => ':missing tienen traducciones faltantes (:percent%).', 18 | 'saved_translation' => 'Traducción guardada', 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/fr/messages.php: -------------------------------------------------------------------------------- 1 | 'Gestionnaire de traductions', 5 | 'navigation_group' => 'Configuration', 6 | 'search_term_placeholder' => 'Rechercher une traduction', 7 | 'selected_groups_placeholder' => 'Sélectionner un groupe', 8 | 'selected_languages_placeholder' => 'Sélectionner une langue', 9 | 'only_show_missing_translations_lbl' => 'Afficher seulement les traductions manquantes', 10 | 'error_no_translations_for_filters' => 'Ajustez les filtres, aucune traduction ne correspond à votre requête!', 11 | 'error_no_translation_loaded' => 'Aucune traduction n\'a été trouvée. Vérifiez votre dossier
lang
', 12 | 'previous_page' => 'Précédent', 13 | 'next_page' => 'Suivant', 14 | 'missing_translation' => 'La traduction est manquante', 15 | 'filter_action' => 'Filtrer', 16 | 'filter_results' => ':filtered éléments filtrés sur :total traductions.', 17 | 'filter_results_missing_translations' => ':missing éléments n\'ont pas de traduction (:percent%).', 18 | 'saved_translation' => 'Traduction sauvegardée', 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/nl/messages.php: -------------------------------------------------------------------------------- 1 | 'Vertalingen', 5 | 'navigation_group' => 'Instellingen', 6 | 'search_term_placeholder' => 'Zoeken', 7 | 'selected_groups_placeholder' => 'Selecteer groep', 8 | 'selected_languages_placeholder' => 'Selecteer taal', 9 | 'only_show_missing_translations_lbl' => 'Toon alleen missende vertalingen', 10 | 'error_no_translations_for_filters' => 'Pas de filters aan, want er zijn geen vertalingen voor jouw zoekopdracht!', 11 | 'error_no_translation_loaded' => 'Er zijn geen vertalingen gevonden. Controleer jouw
lang
folder.', 12 | 'previous_page' => 'Vorige', 13 | 'next_page' => 'Volgende', 14 | 'missing_translation' => 'De vertaling is niet ingevuld', 15 | 'filter_action' => 'Filter', 16 | 'filter_results' => ':filtered gefilterd uit totaal :total vertalingen.', 17 | 'filter_results_missing_translations' => ':missing hebben missende vertalingen (:percent%).', 18 | 'saved_translation' => 'Vertaling opgeslagen', 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/sk/messages.php: -------------------------------------------------------------------------------- 1 | 'Správca prekladov', 5 | 'navigation_group' => 'Nastavenia', 6 | 'search_term_placeholder' => 'Hľadať preklad', 7 | 'selected_groups_placeholder' => 'Vyberte skupinu', 8 | 'selected_languages_placeholder' => 'Vyberte jazyk', 9 | 'only_show_missing_translations_lbl' => 'Zobraziť iba chýbajúce preklady', 10 | 'error_no_translations_for_filters' => 'Upravte filtre, neexistujú žiadne preklady, ktoré by zodpovedali vašej požiadavke!', 11 | 'error_no_translation_loaded' => 'Nenašli sa žiadne preklady. Skontrolujte svoj adresár
lang
', 12 | 'previous_page' => 'Predchádzajúca', 13 | 'next_page' => 'Ďalšia', 14 | 'missing_translation' => 'Preklad nie je vyplnený', 15 | 'filter_action' => 'Filtrovať', 16 | 'filter_results' => 'Vyfiltrované :filtered z :total prekladov.', 17 | 'filter_results_missing_translations' => ':missing má chýbajúce preklady (:percent%).', 18 | 'saved_translation' => 'Preklad uložený', 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/views/livewire/translation-edit-form.blade.php: -------------------------------------------------------------------------------- 1 | 2 |

{{ $group . ' - '. $translationKey }}

3 |
4 | @foreach($locales as $locale) 5 | @php($id = $group.$translationKey.'translations'.$locale) 6 |
39 | 48 | 49 |
50 | @if(isset($translations[$locale]) && !empty(trim($translations[$locale]))) 51 | {{ $translations[$locale] }} 52 | @else 53 | 54 | @lang('filament-translation-manager::messages.missing_translation') 55 | 56 | @endif 57 |
58 |
59 |
63 |
64 | 73 |
74 |
75 | 81 |
82 |
83 |
84 | @endforeach 85 |
86 |
87 | -------------------------------------------------------------------------------- /resources/views/pages/translation-manager-page.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
5 |
6 |
7 |
8 | {{ $this->form }} 9 |
10 | 14 | @lang('filament-translation-manager::messages.filter_action') 15 | 16 |
17 |
18 |
19 |
20 | 21 | 25 | 26 | @lang('filament-translation-manager::messages.filter_results', ['filtered' => $totalFilteredTranslations, 'total' => $totalTranslations]) 27 | @if($totalFilteredTranslations > 0) 28 | 29 | 33 | 34 | @lang('filament-translation-manager::messages.filter_results_missing_translations', ['missing' => $totalMissingFilteredTranslations, 35 | 'percent' => number_format(($totalMissingFilteredTranslations / $totalFilteredTranslations) * 100, 0)]) 36 | @endif 37 |
38 | @forelse($filteredTranslations as $translation) 39 | 46 | @empty 47 | @if(empty($translations)) 48 |
@lang('filament-translation-manager::messages.error_no_translations_for_filters')
49 | @else 50 |
@lang('filament-translation-manager::messages.error_no_translations_for_filters')
51 | @endif 52 | @endforelse 53 | 72 |
73 | -------------------------------------------------------------------------------- /resources/views/widgets/translation-status.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
config('filament.dark_mode'), 7 | ])> 8 | Translation manager 9 |
10 |
11 | Missing translations: {{$missingTranslations}} 12 |
13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /src/FilamentChainedTranslationManagerPlugin.php: -------------------------------------------------------------------------------- 1 | pages([ 30 | TranslationManagerPage::class, 31 | ]) 32 | ->widgets($widgets); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/FilamentTranslationManager.php: -------------------------------------------------------------------------------- 1 | name(static::$name) 20 | ->hasViews() 21 | ->hasTranslations() 22 | ->hasConfigFile(); 23 | } 24 | 25 | public function packageBooted(): void 26 | { 27 | $this->mergeConfigFrom(__DIR__.'/../config/filament-translation-manager.php', 'filament-translation-manager'); 28 | 29 | $supportedLocales = config('filament-translation-manager.locales', config('filament-translation-manager.supported_locales')); 30 | 31 | if (empty($supportedLocales)) { 32 | $supportedLocales = [ 33 | config('app.locale'), 34 | config('app.fallback_locale'), 35 | ]; 36 | } 37 | 38 | FilamentTranslationManager::setLocales($supportedLocales); 39 | 40 | Livewire::component('translation-manager-page', TranslationManagerPage::class); 41 | Livewire::component('translation-edit-form', TranslationEditForm::class); 42 | Livewire::component('translation-status', TranslationStatusWidget::class); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Http/Livewire/TranslationEditForm.php: -------------------------------------------------------------------------------- 1 | initialTranslations = $this->translations; 30 | } 31 | 32 | public function save(string $locale): void 33 | { 34 | $chainedTranslationManager = app(ChainedTranslationManager::class); 35 | 36 | if (($this->translations[$locale] ?? null) === ($this->initialTranslations[$locale] ?? null)) { 37 | return; 38 | } 39 | 40 | $chainedTranslationManager->save( 41 | $locale, 42 | $this->group, 43 | $this->translationKey, 44 | $this->translations[$locale] 45 | ); 46 | 47 | $this->dispatch(self::EVENT_TRANSLATIONS_SAVED, $this->group, $this->translationKey, $this->translations, $this->initialTranslations); 48 | 49 | $this->initialTranslations = $this->translations; 50 | 51 | Notification::make() 52 | ->success() 53 | ->title(trans('filament-translation-manager::messages.saved_translation')) 54 | ->send(); 55 | } 56 | 57 | public function cancel(): void 58 | { 59 | $this->translations = $this->initialTranslations; 60 | } 61 | 62 | public function render(): View 63 | { 64 | return view('filament-translation-manager::livewire.translation-edit-form'); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Pages/TranslationManagerPage.php: -------------------------------------------------------------------------------- 1 | [ 53 | 'except' => 1, 54 | 'as' => 'page', 55 | ], 56 | 'searchTerm' => [ 57 | 'as' => 'search', 58 | 'except' => '', 59 | ], 60 | 'onlyShowMissingTranslations' => [ 61 | 'except' => false, 62 | 'as' => 'showMissing', 63 | ], 64 | 'selectedGroups', 65 | 'selectedLocales', 66 | ]; 67 | 68 | protected $listeners = [TranslationEditForm::EVENT_TRANSLATIONS_SAVED => 'translationsSaved']; 69 | 70 | protected static string $view = 'filament-translation-manager::pages.translation-manager-page'; 71 | 72 | public static function shouldRegisterNavigation(): bool 73 | { 74 | if (config('filament-translation-manager.gate', config('filament-translation-manager.access.gate'))) { 75 | return Gate::allows(config('filament-translation-manager.gate', config('filament-translation-manager.access.gate'))); 76 | } 77 | 78 | return true; 79 | } 80 | 81 | public static function getNavigationGroup(): ?string 82 | { 83 | return trans('filament-translation-manager::messages.navigation_group'); 84 | } 85 | 86 | public static function getNavigationLabel(): string 87 | { 88 | return trans('filament-translation-manager::messages.title'); 89 | } 90 | 91 | public static function getNavigationIcon(): ?string 92 | { 93 | return FilamentIcon::resolve('filament-chained-translation-manager::nav-icon') ?? 94 | config('filament-translation-manager.navigation_icon', 'heroicon-o-language'); 95 | } 96 | 97 | public function getTitle(): string 98 | { 99 | return trans('filament-translation-manager::messages.title'); 100 | } 101 | 102 | public function mount(): void 103 | { 104 | if (config('filament-translation-manager.gate', config('filament-translation-manager.access.gate'))) { 105 | Gate::authorize(config('filament-translation-manager.gate', config('filament-translation-manager.access.gate'))); 106 | } 107 | 108 | $this->loadInitialData(); 109 | } 110 | 111 | private function loadInitialData(): void 112 | { 113 | $groups = $this->getChainedTranslationManager()->getTranslationGroups(); 114 | $this->groups = collect($groups)->diff(config('filament-translation-manager.ignore_groups', []))->values()->toArray(); 115 | 116 | $this->locales = $this->getLocalesData(); 117 | $this->selectedLocales = $this->locales; 118 | 119 | $this->filterTranslations(); 120 | } 121 | 122 | protected function getLocalesData(): array 123 | { 124 | return FilamentTranslationManager::getLocales(); 125 | } 126 | 127 | private function getTranslations(): array 128 | { 129 | $data = []; 130 | 131 | foreach ($this->locales as $locale) { 132 | foreach ($this->groups as $group) { 133 | $this->addTranslationsToData($data, $locale, $group); 134 | } 135 | } 136 | 137 | return array_values($data); 138 | } 139 | 140 | private function addTranslationsToData(array &$data, string $locale, string $group): array 141 | { 142 | $translations = $this->getChainedTranslationManager()->getTranslationsForGroup($locale, $group); 143 | 144 | //transform to data structure necessary for frontend 145 | foreach ($translations as $key => $translation) { 146 | $dataKey = $group.'.'.$key; 147 | if (! array_key_exists($dataKey, $data)) { 148 | $data[$dataKey] = [ 149 | 'title' => $group.' - '.$key, 150 | 'type' => 'group', 151 | 'group' => $group, 152 | 'translation_key' => $key, 153 | 'translations' => [], 154 | ]; 155 | } 156 | $data[$dataKey]['translations'][$locale] = $translation; 157 | } 158 | 159 | return $data; 160 | } 161 | 162 | public function getFormSchema(): array 163 | { 164 | return [ 165 | Grid::make() 166 | ->columns(2) 167 | ->schema([ 168 | Grid::make() 169 | ->columns(6) 170 | ->schema([ 171 | TextInput::make('searchTerm') 172 | ->hiddenLabel() 173 | ->placeholder(trans('filament-translation-manager::messages.search_term_placeholder')) 174 | ->prefixIcon('heroicon-o-magnifying-glass') 175 | ->columnSpan(3), 176 | Grid::make()->schema([ 177 | Checkbox::make('onlyShowMissingTranslations') 178 | ->label(trans('filament-translation-manager::messages.only_show_missing_translations_lbl')) 179 | ->default(false), 180 | ])->columnSpan(3)->columns(1)->extraAttributes(['class' => 'h-full flex items-center']), 181 | ]), 182 | Grid::make() 183 | ->columns(6) 184 | ->schema([ 185 | Select::make('selectedLocales') 186 | ->hiddenLabel() 187 | ->placeholder(trans('filament-translation-manager::messages.selected_languages_placeholder')) 188 | ->multiple() 189 | ->options(array_combine($this->locales, $this->locales)) 190 | ->columnSpan(3), 191 | Select::make('selectedGroups') 192 | ->hiddenLabel() 193 | ->placeholder(trans('filament-translation-manager::messages.selected_groups_placeholder')) 194 | ->multiple() 195 | ->options(array_combine($this->groups, $this->groups)) 196 | ->columnSpan(3), 197 | ]), 198 | ]), 199 | ]; 200 | } 201 | 202 | public function filterTranslations(): void 203 | { 204 | $filteredTranslations = collect($this->getTranslations()); 205 | $this->totalTranslations = $filteredTranslations->count(); 206 | 207 | if ($this->searchTerm) { 208 | $filteredTranslations = $filteredTranslations->filter(function ($translationItem, $key) { 209 | if (Str::contains($translationItem['title'], $this->searchTerm, true)) { 210 | return true; 211 | } 212 | 213 | foreach ($translationItem['translations'] as $translation) { 214 | if (Str::contains($translation, $this->searchTerm, true)) { 215 | return true; 216 | } 217 | } 218 | 219 | return false; 220 | }); 221 | } 222 | 223 | if ($this->onlyShowMissingTranslations) { 224 | $selectedLocales = $this->getFilteredLocales(); 225 | $filteredTranslations = $filteredTranslations->filter(function ($translationItem, $key) use ($selectedLocales) { 226 | return $this->checkIfTranslationMissing($translationItem['translations'], $selectedLocales); 227 | }); 228 | } 229 | 230 | if (! empty($this->selectedGroups)) { 231 | $filteredTranslations = $filteredTranslations->filter(function ($translationItem, $key) { 232 | return in_array($translationItem['group'], $this->selectedGroups, true); 233 | }); 234 | } 235 | 236 | $this->countMissingTranslations($filteredTranslations); 237 | 238 | $filteredTranslations = $this->paginateTranslations($filteredTranslations); 239 | 240 | $this->filteredTranslations = $filteredTranslations; 241 | } 242 | 243 | private function paginateTranslations(Collection $translations): Collection 244 | { 245 | $translations = $translations->sortBy([ 246 | ['group', 'asc'], 247 | ['key', 'asc'], 248 | ]); 249 | 250 | $offset = 0; 251 | if ($this->pageCounter > 1) { 252 | $offset = ($this->pageCounter - 1) * self::PAGE_LIMIT; 253 | } 254 | 255 | $this->pagedTranslations = $offset + self::PAGE_LIMIT; 256 | $this->totalFilteredTranslations = count($translations); 257 | 258 | return $translations->slice($offset, self::PAGE_LIMIT); 259 | } 260 | 261 | private function getChainedTranslationManager(): ChainedTranslationManager 262 | { 263 | if (! isset($this->chainedTranslationManager)) { 264 | $this->chainedTranslationManager = app(ChainedTranslationManager::class); 265 | } 266 | 267 | return $this->chainedTranslationManager; 268 | } 269 | 270 | public function submitFilters(): void 271 | { 272 | $this->pageCounter = 1; 273 | $this->filterTranslations(); 274 | } 275 | 276 | public function previousPage(): void 277 | { 278 | if ($this->pageCounter > 1) { 279 | $this->pageCounter--; 280 | $this->filterTranslations(); 281 | } 282 | } 283 | 284 | public function nextPage(): void 285 | { 286 | if ($this->pageCounter * self::PAGE_LIMIT <= $this->totalFilteredTranslations) { 287 | $this->pageCounter++; 288 | $this->filterTranslations(); 289 | } 290 | } 291 | 292 | private function countMissingTranslations($translations): int 293 | { 294 | $selectedLocales = $this->getFilteredLocales(); 295 | 296 | $count = $translations->reduce(function ($carry, $translationItem) use ($selectedLocales) { 297 | $missing = $this->checkIfTranslationMissing($translationItem['translations'], $selectedLocales); 298 | 299 | return $carry + ($missing ? 1 : 0); 300 | }, 0); 301 | 302 | $this->totalMissingFilteredTranslations = $count; 303 | 304 | return $count; 305 | } 306 | 307 | public function translationsSaved(string $group, string $translationKey, array $newTranslation, ?array $initialTranslations = null): void 308 | { 309 | $oldMissing = $this->checkIfTranslationMissing($initialTranslations, $this->getFilteredLocales()); 310 | $newMissing = $this->checkIfTranslationMissing($newTranslation, $this->getFilteredLocales()); 311 | 312 | if ($oldMissing && ! $newMissing) { 313 | $this->totalMissingFilteredTranslations--; 314 | } elseif (! $oldMissing && $newMissing) { 315 | $this->totalMissingFilteredTranslations++; 316 | } 317 | } 318 | 319 | private function checkIfTranslationMissing(array $translations, array $filteredLocales): bool 320 | { 321 | // Check if all selected locales are available in the translation item, by intersecting the locales of the 322 | // translation item and the selected locales and seeing if the size matches with the selected locales. 323 | if (count(array_intersect($filteredLocales, array_keys($translations))) !== count($filteredLocales)) { 324 | return true; 325 | } 326 | 327 | foreach ($translations as $locale => $translation) { 328 | if (in_array($locale, $filteredLocales, true)) { 329 | if (empty($translation) || trim($translation) === '') { 330 | return true; 331 | } 332 | } 333 | } 334 | 335 | return false; 336 | } 337 | 338 | private function getFilteredLocales(): array 339 | { 340 | return ! empty($this->selectedLocales) ? $this->selectedLocales : $this->locales; 341 | } 342 | 343 | public static function getNavigationSort(): ?int 344 | { 345 | return config('filament-translation-manager.navigation_sort'); 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /src/Widgets/TranslationStatusWidget.php: -------------------------------------------------------------------------------- 1 | 10, 30 | ]; 31 | } 32 | } 33 | --------------------------------------------------------------------------------