├── .editorconfig ├── .gitattributes ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FUNDING.yml └── workflows │ ├── notify.yml │ ├── run-tests.yml │ └── update-changelog.yml ├── .gitignore ├── .styleci.yml ├── LICENSE.md ├── README.md ├── UPGRADING.md ├── composer.json ├── phpunit.xml.dist ├── psalm.xml ├── src ├── Console │ ├── LarexExportCommand.php │ ├── LarexFindCommand.php │ ├── LarexImportCommand.php │ ├── LarexInitCommand.php │ ├── LarexInsertCommand.php │ ├── LarexLangAddCommand.php │ ├── LarexLangOrderCommand.php │ ├── LarexLangRemoveCommand.php │ ├── LarexLintCommand.php │ ├── LarexLocalizeCommand.php │ ├── LarexRemoveCommand.php │ └── LarexSortCommand.php ├── Contracts │ ├── Exporter.php │ ├── Importer.php │ └── Linter.php ├── Exceptions │ ├── ImportException.php │ └── LintException.php ├── Exporters │ ├── JsonGroupsExporter.php │ ├── JsonLanguagesExporter.php │ └── LaravelExporter.php ├── Importers │ ├── JsonGroupsImporter.php │ ├── JsonLanguagesImporter.php │ └── LaravelImporter.php ├── LarexServiceProvider.php ├── Linters │ ├── ConcurrentKeyLinter.php │ ├── DuplicateKeyLinter.php │ ├── DuplicateValueLinter.php │ ├── NoValueLinter.php │ ├── SameParametersLinter.php │ ├── UntranslatedStringsLinter.php │ ├── UnusedStringsLinter.php │ ├── ValidHeaderLinter.php │ ├── ValidHtmlValueLinter.php │ └── ValidLanguageCodeLinter.php ├── Stubs │ ├── base.stub │ └── default.stub ├── Support │ ├── CsvParser.php │ ├── CsvReader.php │ ├── CsvWriter.php │ ├── Languages.php │ └── Utils.php ├── config │ └── larex.php └── helpers.php └── tests ├── Console ├── LarexExportTest.php ├── LarexFindTest.php ├── LarexImportTest.php ├── LarexInitTest.php ├── LarexInsertTest.php ├── LarexLangAddTest.php ├── LarexLangOrderTest.php ├── LarexLangRemoveTest.php ├── LarexLintTest.php ├── LarexLocalizeTest.php ├── LarexRemoveTest.php └── LarexSortTest.php ├── Exporters ├── JsonGroupsExporterTest.php ├── JsonLanguagesExporterTest.php └── LaravelExporterTest.php ├── Importers ├── JsonGroupsImporterTest.php ├── JsonLanguagesImporterTest.php └── LaravelImporterTest.php ├── Linters ├── ConcurrentKeyLinterTest.php ├── DuplicateKeyLinterTest.php ├── DuplicateValueLinterTest.php ├── NoValueLinterTest.php ├── SameParametersLinterTest.php ├── UntranslatedStringsLinterTest.php ├── UnusedStringsLinterTest.php ├── ValidHeaderLinterTest.php ├── ValidHtmlValueLinterTest.php └── ValidLanguageCodeLinterTest.php ├── Pest.php ├── Stubs ├── console │ ├── find │ │ └── input.stub │ ├── insert │ │ ├── base │ │ │ ├── input.stub │ │ │ ├── output-app-en.stub │ │ │ ├── output-app-it.stub │ │ │ └── output.stub │ │ ├── correction │ │ │ ├── input.stub │ │ │ ├── output-app-en.stub │ │ │ ├── output-app-it.stub │ │ │ └── output.stub │ │ ├── exists │ │ │ ├── input.stub │ │ │ ├── output-continue-no.stub │ │ │ └── output-continue-yes.stub │ │ ├── init │ │ │ └── output.stub │ │ └── special │ │ │ ├── input.stub │ │ │ └── output.stub │ ├── lang-add │ │ ├── lang-add-output.stub │ │ └── lang-input.stub │ ├── lang-order │ │ ├── input.stub │ │ └── output.stub │ ├── lang-remove │ │ ├── lang-input.stub │ │ └── lang-remove-output.stub │ ├── lint │ │ ├── failure.stub │ │ ├── no-linters.stub │ │ └── success.stub │ ├── localize │ │ ├── no-strings │ │ │ ├── blade.stub │ │ │ └── csv.stub │ │ ├── with-strings-import │ │ │ ├── blade.stub │ │ │ ├── csv-after.stub │ │ │ └── csv-before.stub │ │ └── with-strings │ │ │ ├── blade.stub │ │ │ └── csv.stub │ ├── remove │ │ ├── abort │ │ │ ├── input.stub │ │ │ └── output.stub │ │ ├── base │ │ │ ├── input.stub │ │ │ └── output.stub │ │ ├── export │ │ │ ├── input.stub │ │ │ ├── output-app-en.stub │ │ │ ├── output-app-it.stub │ │ │ └── output.stub │ │ ├── nostrings │ │ │ ├── input.stub │ │ │ └── output.stub │ │ └── wildcard │ │ │ ├── input.stub │ │ │ └── output.stub │ └── sort │ │ ├── sort-input.stub │ │ └── sort-output.stub ├── exporters │ ├── json-groups │ │ ├── base │ │ │ ├── input.stub │ │ │ ├── output-en-app.stub │ │ │ ├── output-en-special.stub │ │ │ ├── output-it-app.stub │ │ │ └── output-it-special.stub │ │ ├── spaces │ │ │ ├── input.stub │ │ │ ├── output-en-app.stub │ │ │ └── output-it-app.stub │ │ └── warnings │ │ │ ├── input.stub │ │ │ ├── output-en-app.stub │ │ │ └── output-it-app.stub │ ├── json-language │ │ ├── base │ │ │ ├── input.stub │ │ │ ├── output-en.stub │ │ │ └── output-it.stub │ │ ├── include-exclude │ │ │ ├── input.stub │ │ │ ├── output-en.stub │ │ │ └── output-it.stub │ │ ├── spaces │ │ │ ├── input.stub │ │ │ ├── output-en.stub │ │ │ └── output-it.stub │ │ └── warnings │ │ │ ├── input.stub │ │ │ ├── output-en.stub │ │ │ └── output-it.stub │ └── laravel │ │ ├── base │ │ ├── input.stub │ │ ├── output-en-app.stub │ │ ├── output-en-special.stub │ │ ├── output-it-app.stub │ │ └── output-it-special.stub │ │ ├── include-exclude │ │ ├── input.stub │ │ ├── output-en-another.stub │ │ ├── output-en-app.stub │ │ ├── output-it-another.stub │ │ └── output-it-app.stub │ │ ├── spaces │ │ ├── input.stub │ │ ├── output-en.stub │ │ └── output-it.stub │ │ ├── territory │ │ ├── input.stub │ │ ├── output-en_GB.stub │ │ └── output-it.stub │ │ └── warnings │ │ ├── input.stub │ │ ├── output-en.stub │ │ └── output-it.stub ├── importers │ ├── json-groups │ │ ├── base │ │ │ ├── input-en-complex.stub │ │ │ ├── input-en-simple.stub │ │ │ ├── input-it-complex.stub │ │ │ ├── input-it-simple.stub │ │ │ └── output.stub │ │ └── include-exclude │ │ │ ├── exclude.stub │ │ │ ├── include.stub │ │ │ ├── input-en.stub │ │ │ ├── input-fr.stub │ │ │ └── input-it.stub │ ├── json-langs │ │ ├── base │ │ │ ├── input-en.stub │ │ │ ├── input-it.stub │ │ │ └── output.stub │ │ └── include-exclude │ │ │ ├── exclude.stub │ │ │ ├── include.stub │ │ │ ├── input-en.stub │ │ │ ├── input-fr.stub │ │ │ └── input-it.stub │ └── laravel │ │ ├── base │ │ ├── input-en-complex.stub │ │ ├── input-en-simple.stub │ │ ├── input-it-complex.stub │ │ ├── input-it-simple.stub │ │ └── output.stub │ │ ├── include-exclude │ │ ├── exclude.stub │ │ ├── include.stub │ │ ├── input-en.stub │ │ ├── input-fr.stub │ │ └── input-it.stub │ │ ├── source │ │ ├── input-ar.stub │ │ ├── input-en.stub │ │ ├── input-it.stub │ │ ├── output-ar.stub │ │ └── output-en.stub │ │ └── territory │ │ ├── input-en_GB-complex.stub │ │ ├── input-en_GB-simple.stub │ │ ├── input-it-complex.stub │ │ ├── input-it-simple.stub │ │ └── output.stub └── linters │ ├── concurrent-key │ ├── failure.stub │ └── success.stub │ ├── duplicate-key │ ├── failure.stub │ └── success.stub │ ├── duplicate-value │ ├── failure.stub │ └── success.stub │ ├── no-value │ ├── failure.stub │ └── success.stub │ ├── same-parameters │ ├── failure.stub │ └── success.stub │ ├── untranslated-strings │ ├── blade.stub │ ├── failure.stub │ └── success.stub │ ├── unused-strings │ ├── blade.stub │ ├── failure.stub │ └── success.stub │ ├── valid-header │ ├── invalid-first-column.stub │ ├── invalid-second-column.stub │ ├── missing-first-column.stub │ ├── missing-second-column.stub │ ├── no-language-columns.stub │ └── success.stub │ ├── valid-html-value │ ├── failure.stub │ └── success.stub │ └── valid-language-code │ ├── failure-and-suggest.stub │ ├── failure.stub │ └── success.stub └── TestCase.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = false 10 | max_line_length = 120 11 | 12 | [*.{yml, yaml}] 13 | indent_size = 4 14 | 15 | [*.php] 16 | ij_php_align_key_value_pairs = false 17 | ij_php_comma_after_last_array_element = true 18 | ij_php_keep_indents_on_empty_lines = false 19 | ij_php_else_if_style = combine 20 | ij_php_keep_blank_lines_in_code = 1 21 | ij_php_concat_spaces = false 22 | ij_php_blank_lines_before_return_statement = 1 23 | ij_php_space_before_short_closure_left_parenthesis = true 24 | ij_php_anonymous_brace_style = next_line 25 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | * text eol=lf 3 | *.css linguist-vendored 4 | *.scss linguist-vendored 5 | *.js linguist-vendored 6 | CHANGELOG.md export-ignore -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making 6 | participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, 7 | disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, 8 | socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 9 | 10 | ## Our Standards 11 | 12 | Examples of behavior that contributes to creating a positive environment include: 13 | 14 | * Using welcoming and inclusive language 15 | * Being respectful of differing viewpoints and experiences 16 | * Gracefully accepting constructive criticism 17 | * Focusing on what is best for the community 18 | * Showing empathy towards other community members 19 | 20 | Examples of unacceptable behavior by participants include: 21 | 22 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 23 | * Trolling, insulting/derogatory comments, and personal or political attacks 24 | * Public or private harassment 25 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 26 | * Other conduct which could reasonably be considered inappropriate in a professional setting 27 | 28 | ## Our Responsibilities 29 | 30 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take 31 | appropriate and fair corrective action in response to any instances of unacceptable behavior. 32 | 33 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, 34 | issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any 35 | contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 36 | 37 | ## Scope 38 | 39 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the 40 | project or its community. Examples of representing a project or community include using an official project e-mail 41 | address, posting via an official social media account, or acting as an appointed representative at an online or offline 42 | event. Representation of a project may be further defined and clarified by project maintainers. 43 | 44 | ## Enforcement 45 | 46 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at 47 | lucapatera@outlook.it. All complaints will be reviewed and investigated and will result in a response that is deemed 48 | necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to 49 | the reporter of an incident. Further details of specific enforcement policies may be posted separately. 50 | 51 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent 52 | repercussions as determined by other members of the project's leadership. 53 | 54 | ## Attribution 55 | 56 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available 57 | at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 58 | 59 | [homepage]: https://www.contributor-covenant.org 60 | 61 | For answers to common questions about this code of conduct, see 62 | https://www.contributor-covenant.org/faq 63 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | Please read and understand the contribution guide before creating an issue or pull request. 6 | 7 | ## Etiquette 8 | 9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code 10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be 11 | extremely unfair for them to suffer abuse or anger for their hard work. 12 | 13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the 14 | world that developers are civilized and selfless people. 15 | 16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient 17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. 18 | 19 | ## Viability 20 | 21 | When requesting or submitting new features, first consider whether it might be useful to others. Open 22 | source projects are used by many developers, who may have entirely different needs to your own. Think about 23 | whether or not your feature is likely to be used by other users of the project. 24 | 25 | ## Procedure 26 | 27 | Before filing an issue: 28 | 29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. 30 | - Check to make sure your feature suggestion isn't already present within the project. 31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress. 32 | - Check the pull requests tab to ensure that the feature isn't already in progress. 33 | 34 | Before submitting a pull request: 35 | 36 | - Check the codebase to ensure that your feature doesn't already exist. 37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix. 38 | 39 | ## Requirements 40 | 41 | If the project maintainer has any additional requirements, you will find them listed here. 42 | 43 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer). 44 | 45 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 46 | 47 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 48 | 49 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. 50 | 51 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 52 | 53 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 54 | 55 | **Happy coding**! 56 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: Lukasss93 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/notify.yml: -------------------------------------------------------------------------------- 1 | name: Notify 2 | on: 3 | push: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | notify: 9 | name: Notify via Telegram 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Send message to Telegram 13 | uses: Lukasss93/telegram-action@v1.1 14 | env: 15 | TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }} 16 | TELEGRAM_CHAT: ${{ secrets.TELEGRAM_CHAT }} 17 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | psalm: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Setup PHP 14 | uses: shivammathur/setup-php@v2 15 | with: 16 | php-version: '8.0' 17 | coverage: none 18 | 19 | - name: Validate composer.json and composer.lock 20 | run: composer validate 21 | 22 | - name: Install dependencies 23 | run: composer install --no-progress --no-ansi 24 | 25 | - name: Run Psalm 26 | run: composer run-script psalm 27 | 28 | tests: 29 | runs-on: ubuntu-latest 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | php: [8.0, 8.1, 8.2, 8.3, 8.4] 34 | laravel: ['8.*', '9.*', '10.*', '11.*', '12.*'] 35 | include: 36 | - laravel: 8.* 37 | testbench: 6.* 38 | - laravel: 9.* 39 | testbench: 7.* 40 | - laravel: 10.* 41 | testbench: 8.* 42 | - laravel: 11.* 43 | testbench: 9.* 44 | - laravel: 12.* 45 | testbench: 10.* 46 | exclude: 47 | - php: 8.0 48 | laravel: 10.* 49 | - php: 8.0 50 | laravel: 11.* 51 | - php: 8.0 52 | laravel: 12.* 53 | - php: 8.1 54 | laravel: 11.* 55 | - php: 8.1 56 | laravel: 12.* 57 | - php: 8.4 58 | laravel: 8.* 59 | 60 | 61 | name: Laravel ${{ matrix.laravel }} with PHP ${{ matrix.php }} 62 | steps: 63 | - name: Checkout code 64 | uses: actions/checkout@v4 65 | 66 | - name: Setup PHP 67 | uses: shivammathur/setup-php@v2 68 | with: 69 | php-version: ${{ matrix.php }} 70 | coverage: none 71 | 72 | - name: Install dependencies 73 | run: | 74 | composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update 75 | composer require "orchestra/testbench:${{ matrix.testbench }}" --dev --no-interaction --no-update 76 | composer update --prefer-dist --no-interaction 77 | 78 | - name: Execute tests 79 | run: composer run-script test 80 | 81 | coverage: 82 | name: Coverage 83 | needs: 84 | - tests 85 | runs-on: ubuntu-latest 86 | steps: 87 | - uses: actions/checkout@v4 88 | 89 | - name: Setup PHP 90 | uses: shivammathur/setup-php@v2 91 | with: 92 | php-version: 8.0 93 | coverage: xdebug 94 | 95 | - name: Validate composer.json and composer.lock 96 | run: composer validate 97 | 98 | - name: Install dependencies 99 | run: composer install --prefer-dist --no-interaction 100 | 101 | - name: Run test suite 102 | uses: paambaati/codeclimate-action@v9 103 | env: 104 | CC_TEST_REPORTER_ID: ${{ secrets.CC_REPORTER_ID }} 105 | with: 106 | coverageCommand: composer run-script test-coverage 107 | coverageLocations: ${{github.workspace}}/coverage.xml:clover 108 | -------------------------------------------------------------------------------- /.github/workflows/update-changelog.yml: -------------------------------------------------------------------------------- 1 | name: "Update Changelog" 2 | 3 | on: 4 | release: 5 | types: [ released ] 6 | 7 | jobs: 8 | update: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | with: 15 | ref: master 16 | 17 | - name: Update Changelog 18 | uses: stefanzweifel/changelog-updater-action@v1 19 | with: 20 | latest-version: ${{ github.event.release.tag_name }} 21 | release-notes: ${{ github.event.release.body }} 22 | 23 | - name: Commit updated CHANGELOG 24 | uses: stefanzweifel/git-auto-commit-action@v4 25 | with: 26 | branch: master 27 | commit_message: Update CHANGELOG 28 | file_pattern: CHANGELOG.md 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | composer.lock 3 | /vendor/ 4 | phpinsights.json 5 | coverage.xml 6 | composer.phar 7 | /.phpunit.cache/ 8 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | risky: true 3 | 4 | disabled: 5 | - not_operator_with_successor_space 6 | - cast_spaces 7 | - laravel_phpdoc_alignment 8 | - laravel_phpdoc_separation 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 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 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | # Upgrading 2 | 3 | Because there are many breaking changes an upgrade is not that easy. There are many edge cases this guide does not 4 | cover. We accept PRs to improve this guide. 5 | 6 | ## From v4.2 to v4.3.0 7 | 8 | - The `larex.php` config was changed: 9 | - **Optional**. Please append new `source_language` key in this way: 10 | ```php 11 | 'source_language' => 'en', 12 | ``` 13 | 14 | _Or you can delete your current config and publish it again (copy your changes before)._ 15 | 16 | ## From v4.0 to v4.2 17 | 18 | - The `larex.php` config was changed: 19 | - **Optional**. Please append new `SameParametersLinter` linter in this way: 20 | ```php 21 | 'linters' => [ 22 | // other linters here 23 | // Lukasss93\Larex\Linters\SameParametersLinter::class, 24 | ], 25 | ``` 26 | - **Optional**. Please append `ignore_empty_values` key in this way: 27 | ```php 28 | 'ignore_empty_values' => false, 29 | ``` 30 | _Or you can delete your current config and publish it again (copy your changes before)._ 31 | 32 | ## From v3.x to v4.0 33 | - The `larex.php` config was changed. Please change the `path` key in this way: 34 | ```php 35 | // /config/larex.php 36 | 37 | // before 38 | 'path' => 'resources/lang/localization.csv', 39 | 40 | // after (Laravel 9) 41 | 'path' => lang_path('localization.csv'), 42 | 43 | // after (Laravel 8) 44 | 'path' => resource_path('lang/localization.csv'), 45 | ``` 46 | 47 | _Or you can delete your current config and publish it again (copy your changes before)._ 48 | - ⚠️ Dropped **PHP 7.4** support, please upgrade at least to **PHP 8.0**. 49 | - ⚠️ Dropped **Laravel 7** support, please upgrade at least to **Laravel 8**. 50 | 51 | 52 | ## From v2.1 to v3.x 53 | 54 | - The `larex.php` config was changed. Please delete your current config and publish it again (copy your changes before). 55 | - If you created a custom Linter, please follow this steps: 56 | - Change `public function description()` to `public static function description()` 57 | - Change `public function handle(Collection $row)` to `public function handle(CsvReader $reader)` 58 | - The CsvReader class exposes the following methods: 59 | - `getHeader` to get the CSV header as `Collection` 60 | - `getRows` to get the CSV rows as `LazyCollection` 61 | 62 | - ⚠️ Dropped **PHP 7.3** support, please upgrade at least to **PHP 7.4**. 63 | - ⚠️ Dropped **Laravel 6** support, please upgrade at least to **Laravel 7**. 64 | 65 | ## From v1.6 to v2.0 66 | 67 | Because the CSV default format was changed, please follow this steps: 68 | 69 | 1. ⚠️ Please run the `php artisan larex:export` **before upgrading to v2.0!** 70 | 2. Delete the `localization.csv` file inside _project-root/resources/lang_ folder 71 | 3. Upgrade the library to **v2.0** 72 | 4. Run the `php artisan larex:import` command 73 | 74 | ## From v1.5 to v1.6 75 | 76 | - No breaking changes. 77 | - ⚠️ Dropped support for **PHP 7.2**, please upgrade at least to **PHP 7.3**. 78 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lukasss93/laravel-larex", 3 | "description": "Translate your Laravel application from a single CSV file!", 4 | "keywords": [ 5 | "laravel", 6 | "localization", 7 | "translation", 8 | "i18n", 9 | "csv" 10 | ], 11 | "type": "library", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Luca Patera", 16 | "email": "lucapatera@outlook.it", 17 | "homepage": "https://www.lucapatera.it/", 18 | "role": "Developer" 19 | } 20 | ], 21 | "minimum-stability": "dev", 22 | "prefer-stable": true, 23 | "require": { 24 | "php": "^8.0", 25 | "ext-dom": "*", 26 | "ext-json": "*", 27 | "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0", 28 | "loilo/fuse": "^6.4.6|^7.1", 29 | "spatie/simple-excel": "^3.0.1", 30 | "spatie/laravel-collection-macros": "^7.12|^8.0" 31 | }, 32 | "require-dev": { 33 | "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0|^10.0", 34 | "pestphp/pest": "^v1.0|^v2.0|^3.7", 35 | "vimeo/psalm": "^5.0|^6.7" 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "Lukasss93\\Larex\\": "src/" 40 | }, 41 | "files": [ 42 | "src/helpers.php" 43 | ] 44 | }, 45 | "autoload-dev": { 46 | "psr-4": { 47 | "Lukasss93\\Larex\\Tests\\": "tests/" 48 | }, 49 | "files": [ 50 | "src/helpers.php" 51 | ] 52 | }, 53 | "extra": { 54 | "laravel": { 55 | "providers": [ 56 | "Lukasss93\\Larex\\LarexServiceProvider" 57 | ] 58 | } 59 | }, 60 | "config": { 61 | "sort-packages": true, 62 | "allow-plugins": { 63 | "pestphp/pest-plugin": true 64 | } 65 | }, 66 | "scripts": { 67 | "test": "@php ./vendor/bin/pest --colors=always", 68 | "test-coverage": "@php ./vendor/bin/pest --colors=always --coverage-clover=coverage.xml", 69 | "psalm": "@php ./vendor/bin/psalm", 70 | "psalm-info": "@php ./vendor/bin/psalm --show-info=true" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | tests 16 | 17 | 18 | 19 | 20 | src/ 21 | 22 | src/Support 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Console/LarexExportCommand.php: -------------------------------------------------------------------------------- 1 | option('watch')) { 39 | return $this->watch(); 40 | } 41 | 42 | return $this->translate(); 43 | } 44 | 45 | protected function watch(): int 46 | { 47 | $this->warn(sprintf("Watching the '%s' file...", csv_path(true))); 48 | 49 | $lastEditDate = null; 50 | Utils::forever(function () use (&$lastEditDate) { 51 | $currentEditDate = filemtime(csv_path()); 52 | clearstatcache(); 53 | 54 | if ($lastEditDate !== $currentEditDate) { 55 | $lastEditDate = $currentEditDate; 56 | $this->translate(); 57 | $this->line('Waiting for changes...'); 58 | } 59 | 60 | usleep(500 * 1000); 61 | }); 62 | 63 | return 0; 64 | } 65 | 66 | protected function translate(): int 67 | { 68 | $this->warn(sprintf("Processing the '%s' file...", csv_path(true))); 69 | 70 | //check if csv file exists 71 | if (!File::exists(csv_path())) { 72 | $this->error(sprintf("The '%s' does not exists.", csv_path(true))); 73 | $this->line('Please create it with: php artisan larex:init or php artisan larex:import'); 74 | 75 | return 1; 76 | } 77 | 78 | //check concurrent options 79 | if ($this->option('include') !== null && $this->option('exclude') !== null) { 80 | $this->error('The --include and --exclude options can be used only one at a time.'); 81 | 82 | return 1; 83 | } 84 | 85 | //csv reader 86 | $reader = CsvReader::create(csv_path()); 87 | 88 | //get the exporter name 89 | $exporterKey = $this->argument('exporter') ?? config('larex.exporters.default'); 90 | $exporters = config('larex.exporters.list'); 91 | 92 | //check if exporter exists 93 | if (!array_key_exists($exporterKey, $exporters)) { 94 | $this->error("Exporter '$exporterKey' not found."); 95 | $this->line(''); 96 | $this->info('Available exporters:'); 97 | foreach ($exporters as $key => $exporter) { 98 | $description = $exporter::description(); 99 | $this->line("$key - $description"); 100 | } 101 | $this->line(''); 102 | 103 | return 1; 104 | } 105 | 106 | //initialize exporter 107 | $exporter = new $exporters[$exporterKey](); 108 | 109 | //check if exporter is valid 110 | if (!($exporter instanceof Exporter)) { 111 | $this->error(sprintf("Exporter '%s' must implements %s interface.", $exporterKey, Exporter::class)); 112 | 113 | return 1; 114 | } 115 | 116 | //call the exporter 117 | return $exporter->handle($this, $reader); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Console/LarexFindCommand.php: -------------------------------------------------------------------------------- 1 | option('width'); 35 | 36 | $value = Str::lower($this->argument('value')); 37 | $find = Str::of($value)->explode('.'); 38 | $findGroup = $find->first(); 39 | $findKey = $find->skip(1)->implode('.'); 40 | 41 | if (!File::exists(csv_path())) { 42 | $this->error(sprintf("The '%s' does not exists.", csv_path(true))); 43 | $this->line('Please create it with: php artisan larex:init or php artisan larex:import'); 44 | 45 | return 1; 46 | } 47 | 48 | $reader = CsvReader::create(csv_path()); 49 | 50 | //get headers 51 | $headers = $reader 52 | ->getHeader() 53 | ->take(3); 54 | 55 | //get rows 56 | $result = $reader 57 | ->getRows() 58 | ->filter(fn ($item) => Str::contains(Str::lower("{$item['group']}.{$item['key']}"), $value)) 59 | ->map(fn ($item) => [ 60 | str_replace($findGroup, "$findGroup", $item['group']), 61 | str_replace([$findGroup, $findKey], ["$findGroup", "$findKey"], 62 | $item['key']), 63 | $item[$headers->get(2)], 64 | ]) 65 | ->collect(); 66 | 67 | if ($result->isEmpty()) { 68 | $this->line('No string found.'); 69 | 70 | return 0; 71 | } 72 | 73 | $table = new Table($this->output); 74 | $table 75 | ->setHeaders($headers->toArray()) 76 | ->setRows($result->toArray()) 77 | ->setColumnMaxWidth(2, $width) 78 | ->render(); 79 | 80 | return 0; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Console/LarexInitCommand.php: -------------------------------------------------------------------------------- 1 | option('base')) { 35 | $stub = 'base'; 36 | } 37 | 38 | if (File::exists(csv_path())) { 39 | $this->error(csv_path(true).' already exists.'); 40 | 41 | return 1; 42 | } 43 | 44 | Utils::filePut(csv_path(), Utils::getStub($stub)); 45 | 46 | $this->info(csv_path(true).' created successfully.'); 47 | 48 | return 0; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Console/LarexInsertCommand.php: -------------------------------------------------------------------------------- 1 | error(sprintf("The '%s' does not exists.", csv_path(true))); 39 | $this->line('Please create it with: php artisan larex:init'); 40 | 41 | return 1; 42 | } 43 | 44 | //csv reader 45 | $reader = CsvReader::create(csv_path()); 46 | 47 | //get csv header 48 | $header = $reader->getHeader(); 49 | 50 | //get csv rows 51 | $rows = $reader->getRows()->collect(); 52 | 53 | //get existing groups 54 | $availableGroups = $rows 55 | ->pluck('group') 56 | ->unique() 57 | ->toArray(); 58 | 59 | //get existing keys 60 | $availableKeys = $rows 61 | ->pluck('key') 62 | ->unique() 63 | ->map(fn ($item) => "$item.") 64 | ->toArray(); 65 | 66 | //get available languages 67 | $languages = $header->skip(2)->values(); 68 | 69 | //initialize data 70 | /** @var Collection $data */ 71 | $data = collect([]); 72 | 73 | //iterate until user confirm the inserted data 74 | do { 75 | do { 76 | $continue = true; 77 | 78 | //get group 79 | do { 80 | $group = trim($this->anticipate('Enter the group', $availableGroups, $data->get('group'))); 81 | 82 | if ($group === '') { 83 | $this->error('Please enter a group!'); 84 | } 85 | } while ($group === ''); 86 | $data->put('group', $group); 87 | 88 | //get key 89 | do { 90 | $key = trim($this->anticipate('Enter the key', $availableKeys, $data->get('key'))); 91 | 92 | if ($key === '') { 93 | $this->error('Please enter a key!'); 94 | } 95 | } while ($key === ''); 96 | $data->put('key', $key); 97 | 98 | if ($rows->contains('group', $group) && $rows->contains('key', $key)) { 99 | $continue = $this->askWithCompletion('The group/key pair already exists. Do you want to continue?', ['yes', 'no'], 'no') === 'yes'; 100 | } 101 | } while (!$continue); 102 | 103 | foreach ($languages as $i => $language) { 104 | $count = $i + 1; 105 | $value = $this->ask( 106 | "[$count/{$languages->count()}] Enter the value for [$language] language", 107 | $data->get($language) 108 | ); 109 | 110 | $data->put($language, $value); 111 | } 112 | 113 | $table = new Table($this->output); 114 | /** @var Collection $tableRows */ 115 | $tableRows = collect([]); 116 | $tableRows->push([new TableCell('Summary', ['colspan' => 2])]); 117 | $tableRows->push(new TableSeparator()); 118 | 119 | $count = 0; 120 | foreach ($data as $i => $item) { 121 | $count++; 122 | $tableRows->push(["$i", $item]); 123 | 124 | if ($count < $data->count()) { 125 | $tableRows->push(new TableSeparator()); 126 | } 127 | } 128 | $table->setRows($tableRows->toArray()); 129 | $table->render(); 130 | } while ($this->askWithCompletion('Are you sure?', ['yes', 'no'], 'yes') !== 'yes'); 131 | 132 | //append to csv 133 | $rows->push($data->toArray()); 134 | 135 | CsvWriter::create(csv_path()) 136 | ->addRows($rows->toArray()); 137 | 138 | $this->info('Item added successfully.'); 139 | 140 | $export = $this->option('export'); 141 | if ($export !== 'notset') { 142 | $export = $export === true ? null : $export; 143 | $this->line(''); 144 | $this->call(LarexExportCommand::class, ['exporter' => $export]); 145 | } 146 | 147 | return 0; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/Console/LarexLangAddCommand.php: -------------------------------------------------------------------------------- 1 | argument('code'); 36 | 37 | //check if code is valid 38 | $suggest = Utils::isValidLanguageCode($code); 39 | 40 | if ($suggest === false) { 41 | $this->error("Invalid language code ($code)"); 42 | 43 | return 1; 44 | } 45 | 46 | if (is_string($suggest)) { 47 | $this->warn("Language code is not valid ($code). Did you mean: $suggest?"); 48 | 49 | return 1; 50 | } 51 | 52 | //get CSV file 53 | $content = CsvReader::create(csv_path()) 54 | ->getRows() 55 | ->collect() 56 | ->map(function (Collection $row) use ($code) { 57 | return $row->put($code, null); 58 | }); 59 | 60 | //write new language code to CSV file 61 | CsvWriter::create(csv_path()) 62 | ->addRows($content->toArray()); 63 | 64 | $this->info("Added language column: \"$code\""); 65 | 66 | return 0; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Console/LarexLangOrderCommand.php: -------------------------------------------------------------------------------- 1 | argument('from'); 39 | 40 | /** @var string $to */ 41 | $to = $this->argument('to'); 42 | 43 | //check if csv file exists 44 | if (!File::exists(csv_path())) { 45 | $this->error(sprintf("The '%s' does not exists.", csv_path(true))); 46 | $this->line('Please create it with: php artisan larex:init'); 47 | 48 | return 1; 49 | } 50 | 51 | //read CSV file 52 | $csvReader = CsvReader::create(csv_path()); 53 | 54 | //get available languages 55 | /** @var Collection $languages */ 56 | $languages = $csvReader->getHeader()->skip(2)->values(); 57 | 58 | $content = $csvReader->getRows()->collect(); 59 | 60 | try { 61 | //get source and destination indexes 62 | $sourceIndex = $this->getLanguageIndex($from, $languages, 'source'); 63 | $destinationIndex = $this->getLanguageIndex($to, $languages, 'destination'); 64 | 65 | //check if index are the same 66 | if ($sourceIndex === $destinationIndex) { 67 | throw new RuntimeException('The source and destination languages are the same.'); 68 | } 69 | 70 | //pull source language 71 | $pulledLanguage = $languages->pull($sourceIndex); 72 | 73 | //insert source language in destination position 74 | $languages = $languages->insertAt($destinationIndex, $pulledLanguage); 75 | 76 | //write csv 77 | $csvWriter = CsvWriter::create(csv_path()); 78 | $content->each(function (Collection $row) use ($csvWriter, $languages) { 79 | $item = collect([]); 80 | $item->put('group', $row->get('group')); 81 | $item->put('key', $row->get('key')); 82 | $languages->each(fn ($language) => $item->put($language, $row->get($language))); 83 | $csvWriter->addRow($item->toArray()); 84 | }); 85 | 86 | $this->info('Done.'); 87 | } catch (Throwable $e) { 88 | $this->error($e->getMessage()); 89 | 90 | return 1; 91 | } 92 | 93 | return 0; 94 | } 95 | 96 | protected function getLanguageIndex(string $language, Collection $languages, string $type): int 97 | { 98 | if (is_numeric($language)) { 99 | $position = (int)$language; 100 | if (!$languages->has($position - 1)) { 101 | throw new RuntimeException(sprintf('The %s language (%s) is not valid.', $type, $language)); 102 | } 103 | 104 | return $position - 1; 105 | } 106 | 107 | $index = $languages->search($language); 108 | 109 | if ($index === false) { 110 | throw new RuntimeException(sprintf('The %s language (%s) is not valid.', $type, $language)); 111 | } 112 | 113 | return $index; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Console/LarexLangRemoveCommand.php: -------------------------------------------------------------------------------- 1 | argument('code'); 35 | 36 | //get CSV file 37 | $csv = CsvReader::create(csv_path()); 38 | 39 | //get languages 40 | $languages = $csv->getHeader()->skip(2)->values(); 41 | 42 | //check if language code is present 43 | if (!$languages->contains($code)) { 44 | $this->error("The language code \"$code\" is not present in the CSV file."); 45 | 46 | return 1; 47 | } 48 | 49 | //remove language code 50 | $content = $csv 51 | ->getRows() 52 | ->collect() 53 | ->map(function (Collection $row) use ($code) { 54 | return $row->forget($code); 55 | }); 56 | 57 | //write new language code to CSV file 58 | CsvWriter::create(csv_path()) 59 | ->addRows($content->toArray()); 60 | 61 | $this->info("The language code \"$code\" has been removed from the CSV file."); 62 | 63 | return 0; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Console/LarexLintCommand.php: -------------------------------------------------------------------------------- 1 | error(sprintf("The '%s' does not exists.", csv_path(true))); 39 | $this->line('Please create it with: php artisan larex:init'); 40 | 41 | return 1; 42 | } 43 | 44 | //get linters 45 | /** @var Linter[] $linters */ 46 | $linters = config('larex.linters'); 47 | 48 | /** @var Collection $status */ 49 | $status = collect([]); 50 | 51 | $timeStart = microtime(true); 52 | $memoryStart = memory_get_usage(); 53 | 54 | foreach ($linters as $linter) { 55 | $instance = new $linter; 56 | 57 | if ($instance instanceof Linter) { 58 | $this->warn($instance::description()); 59 | 60 | try { 61 | $instance->handle(CsvReader::create(csv_path())); 62 | 63 | $this->line(' PASS Lint passed successfully.'); 64 | $status->push(true); 65 | } catch (LintException $e) { 66 | $this->line(" FAIL {$e->getMessage()}"); 67 | $status->push(false); 68 | $errors = $e->getErrors(); 69 | $totalErrors = count($errors); 70 | foreach ($errors as $i => $error) { 71 | $char = $i < $totalErrors - 1 ? '├' : '└'; 72 | $this->line("$char $error"); 73 | } 74 | } 75 | $this->line(''); 76 | } 77 | } 78 | 79 | $this->line('........................'); 80 | $this->line(''); 81 | 82 | $time = Utils::msToHuman((int) ((microtime(true) - $timeStart) * 1000)); 83 | $memory = Utils::bytesToHuman(memory_get_usage() - $memoryStart); 84 | 85 | $this->line("Time: $time Memory: $memory"); 86 | $this->line(''); 87 | 88 | $total = $status->count(); 89 | $pass = $status->filter()->count(); 90 | $fail = $status->reject()->count(); 91 | 92 | if ($total === 0) { 93 | //no linters processed 94 | $this->line('No linters executed!'); 95 | 96 | return 1; 97 | } 98 | 99 | if ($total !== $pass) { 100 | //some test fails 101 | $this->line('FAILURES!'); 102 | $this->line("Linters: $total, Failures: $fail"); 103 | 104 | return 1; 105 | } 106 | 107 | //all test passes 108 | $linters = Str::plural('linter', $pass); 109 | $this->line("OK ($pass $linters)"); 110 | 111 | return 0; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Console/LarexLocalizeCommand.php: -------------------------------------------------------------------------------- 1 | getRows()->collect(); 36 | $filesFound = Utils::findFiles(config('larex.search.dirs'), config('larex.search.patterns')); 37 | 38 | $stringsSaved = $csvRows->map(fn ($item) => "{$item['group']}.{$item['key']}")->values(); 39 | $stringsFound = Utils::parseStrings($filesFound, config('larex.search.functions')); 40 | 41 | $unlocalizedStrings = $stringsFound 42 | ->reject(fn ($item) => $stringsSaved->contains($item['string'])) 43 | ->groupBy('filename') 44 | ->map->sortBy('line') 45 | ->map->values() 46 | ->flatten(1); 47 | 48 | if ($unlocalizedStrings->isEmpty()) { 49 | $this->info('No unlocalized strings found.'); 50 | 51 | return 0; 52 | } 53 | 54 | $subject = Str::plural('string', $unlocalizedStrings->count()); 55 | $this->warn("{$unlocalizedStrings->count()} unlocalized $subject found:"); 56 | 57 | foreach ($unlocalizedStrings as $item) { 58 | $this->line("{$item['string']} is untranslated at line {$item['line']}, column {$item['column']} in {$item['filepath']}"); 59 | } 60 | 61 | if ($this->option('import')) { 62 | $this->line(''); 63 | $this->warn('Adding unlocalized string to CSV file...'); 64 | 65 | $languages = $reader->getHeader()->skip(2); 66 | 67 | $unlocalizedStringsToAdd = $unlocalizedStrings 68 | ->map(function ($item) use ($languages) { 69 | $couple = Str::of($item['string'])->explode('.'); 70 | 71 | $output = collect([]) 72 | ->put('group', $couple->get(0)) 73 | ->put('key', $couple->skip(1)->implode('.')); 74 | 75 | foreach ($languages as $lang) { 76 | $output->put($lang, ''); 77 | } 78 | 79 | return $output->toArray(); 80 | }); 81 | 82 | CsvWriter::create(csv_path()) 83 | ->addRows($csvRows->toArray()) 84 | ->addRows($unlocalizedStringsToAdd->toArray()); 85 | 86 | $this->info('Done.'); 87 | } 88 | 89 | return 0; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Console/LarexRemoveCommand.php: -------------------------------------------------------------------------------- 1 | argument('key'); 38 | $force = $this->option('force'); 39 | 40 | if (!File::exists(csv_path())) { 41 | $this->error(sprintf("The '%s' does not exists.", csv_path(true))); 42 | $this->line('Please create it with: php artisan larex:init'); 43 | 44 | return 1; 45 | } 46 | 47 | /** @var Collection $remove */ 48 | /** @var Collection $keep */ 49 | [$remove, $keep] = CsvReader::create(csv_path()) 50 | ->getRows() 51 | ->partition(fn ($item) => Str::is($key, "{$item['group']}.{$item['key']}")) 52 | ->collect(); 53 | 54 | if ($remove->isEmpty()) { 55 | $this->warn('No strings found to remove.'); 56 | 57 | return 0; 58 | } 59 | 60 | $this->warn($remove->count().' '.Str::plural('string', $remove->count()).' found to remove:'); 61 | $remove->values()->each(function ($item, $key) use ($remove) { 62 | $char = $key < $remove->count() - 1 ? '├' : '└'; 63 | $this->line("$char {$item['group']}.{$item['key']}"); 64 | }); 65 | 66 | if ($force || $this->confirm("Are you sure you want to delete {$remove->count()} ".Str::plural('string', 67 | $remove->count()).'?')) { 68 | CsvWriter::create(csv_path())->addRows($keep->toArray()); 69 | 70 | $this->info('Removed successfully.'); 71 | 72 | $export = $this->option('export'); 73 | if ($export !== 'notset') { 74 | $export = $export === true ? null : $export; 75 | $this->line(''); 76 | $this->call(LarexExportCommand::class, ['exporter' => $export]); 77 | } 78 | 79 | return 0; 80 | } 81 | 82 | $this->line('Aborted.'); 83 | 84 | return 0; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Console/LarexSortCommand.php: -------------------------------------------------------------------------------- 1 | warn('Sorting che CSV rows...'); 34 | 35 | if (!File::exists(csv_path())) { 36 | $this->error(sprintf("The '%s' does not exists.", csv_path(true))); 37 | $this->line('Please create it with: php artisan larex:init'); 38 | 39 | return 1; 40 | } 41 | 42 | $content = CsvReader::create(csv_path()) 43 | ->getRows() 44 | ->sortBy(fn ($item) => [$item['group'], $item['key']]) 45 | ->collect(); 46 | 47 | CsvWriter::create(csv_path()) 48 | ->addRows($content->toArray()); 49 | 50 | $this->info('Sorting completed.'); 51 | 52 | return 0; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Contracts/Exporter.php: -------------------------------------------------------------------------------- 1 | errors = $errors; 17 | } 18 | 19 | /** 20 | * Get errors. 21 | * @return string[] 22 | */ 23 | public function getErrors(): array 24 | { 25 | return $this->errors; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Exporters/JsonGroupsExporter.php: -------------------------------------------------------------------------------- 1 | setHandleSubKey(false)->parse(); 31 | 32 | foreach ($parser->getWarnings() as $warning) { 33 | $command->warn($warning); 34 | } 35 | 36 | $include = $command->option('include') !== null ? (explode(',', $command->option('include'))) : []; 37 | $exclude = $command->option('exclude') !== null ? explode(',', $command->option('exclude')) : []; 38 | 39 | //finally save the files 40 | $found = 0; 41 | foreach ($languages as $language => $groups) { 42 | if (count($include) > 0 && !in_array($language, $include, true)) { 43 | continue; 44 | } 45 | if (count($exclude) > 0 && in_array($language, $exclude, true)) { 46 | continue; 47 | } 48 | $found++; 49 | 50 | if (!File::exists(lang_path("$language/"))) { 51 | File::makeDirectory(lang_path("$language/")); 52 | } 53 | 54 | foreach ($groups as $group => $keys) { 55 | Utils::putJson(lang_path("$language/$group.json"), $keys); 56 | $command->info(sprintf('%s created successfully.', lang_rpath("$language/$group.json"))); 57 | } 58 | } 59 | 60 | if ($found === 0) { 61 | $command->info('No entries exported.'); 62 | } 63 | 64 | return 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Exporters/JsonLanguagesExporter.php: -------------------------------------------------------------------------------- 1 | setHandleSubKey(false)->parse(); 30 | 31 | foreach ($parser->getWarnings() as $warning) { 32 | $command->warn($warning); 33 | } 34 | 35 | $include = $command->option('include') !== null ? (explode(',', $command->option('include'))) : []; 36 | $exclude = $command->option('exclude') !== null ? explode(',', $command->option('exclude')) : []; 37 | 38 | //finally save the files 39 | $found = 0; 40 | foreach ($languages as $language => $groups) { 41 | if (count($include) > 0 && !in_array($language, $include, true)) { 42 | continue; 43 | } 44 | if (count($exclude) > 0 && in_array($language, $exclude, true)) { 45 | continue; 46 | } 47 | $found++; 48 | 49 | $data = []; 50 | foreach ($groups as $group => $keys) { 51 | foreach ($keys as $key => $value) { 52 | $data["$group.$key"] = $value; 53 | } 54 | } 55 | 56 | Utils::putJson(lang_path("$language.json"), $data); 57 | $command->info(sprintf('%s created successfully.', lang_rpath("$language.json"))); 58 | } 59 | 60 | if ($found === 0) { 61 | $command->info('No entries exported.'); 62 | } 63 | 64 | return 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Exporters/LaravelExporter.php: -------------------------------------------------------------------------------- 1 | parse(); 28 | 29 | foreach ($parser->getWarnings() as $warning) { 30 | $command->warn($warning); 31 | } 32 | 33 | $include = $command->option('include') !== null ? (explode(',', $command->option('include'))) : []; 34 | $exclude = $command->option('exclude') !== null ? explode(',', $command->option('exclude')) : []; 35 | $eol = config('larex.eol', PHP_EOL); 36 | 37 | //finally save the files 38 | $found = 0; 39 | foreach ($languages as $language => $groups) { 40 | if (count($include) > 0 && !in_array($language, $include, true)) { 41 | continue; 42 | } 43 | if (count($exclude) > 0 && in_array($language, $exclude, true)) { 44 | continue; 45 | } 46 | $found++; 47 | 48 | $folder = str_replace('-', '_', $language); 49 | 50 | if (!File::exists(lang_path("$folder/"))) { 51 | File::makeDirectory(lang_path("$folder/")); 52 | } 53 | 54 | foreach ($groups as $group => $keys) { 55 | $write = fopen(lang_path("$folder/$group.php"), 'wb'); 56 | fwrite($write, /** @lang text */ " $value) { 59 | self::writeKeyValue($key, $value, $write, 1, $eol); 60 | } 61 | 62 | fwrite($write, "$eol];$eol"); 63 | 64 | fclose($write); 65 | $command->info(sprintf('%s created successfully.', lang_rpath("$folder/$group.php"))); 66 | } 67 | } 68 | 69 | if ($found === 0) { 70 | $command->info('No entries exported.'); 71 | } 72 | 73 | return 0; 74 | } 75 | 76 | protected static function writeKeyValue($key, $value, &$file, int $level = 1, $eol = PHP_EOL): void 77 | { 78 | $enclosure = '"'; 79 | 80 | if (is_array($value)) { 81 | fwrite($file, str_repeat(' ', $level)."'$key' => [$eol"); 82 | $level++; 83 | foreach ($value as $childKey => $childValue) { 84 | self::writeKeyValue($childKey, $childValue, $file, $level, $eol); 85 | } 86 | fwrite($file, str_repeat(' ', $level - 1)."],$eol"); 87 | 88 | return; 89 | } 90 | 91 | $value = (string)$value; 92 | $value = str_replace(["'", '\\'.$enclosure], ["\'", $enclosure], $value); 93 | 94 | if (is_int($key) || (is_numeric($key) && ctype_digit($key))) { 95 | $key = (int)$key; 96 | fwrite($file, str_repeat(' ', $level)."$key => '$value',$eol"); 97 | } else { 98 | fwrite($file, str_repeat(' ', $level)."'$key' => '$value',$eol"); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Importers/JsonGroupsImporter.php: -------------------------------------------------------------------------------- 1 | option('include'))->explode(',')->reject(fn ($i) => empty($i)); 29 | $exclude = Str::of($command->option('exclude'))->explode(',')->reject(fn ($i) => empty($i)); 30 | 31 | /** @var Collection $languages */ 32 | $languages = collect([]); 33 | 34 | /** @var Collection $rawValues */ 35 | $rawValues = collect([]); 36 | 37 | //get all files 38 | $files = File::glob(lang_path('**/*.json')); 39 | 40 | foreach ($files as $file) { 41 | $items = json_decode(File::get($file), true, 512, JSON_THROW_ON_ERROR); 42 | $group = pathinfo($file, PATHINFO_FILENAME); 43 | $lang = basename(dirname($file)); 44 | 45 | if ($include->isNotEmpty() && !$include->contains($lang)) { 46 | continue; 47 | } 48 | 49 | if ($exclude->isNotEmpty() && $exclude->contains($lang)) { 50 | continue; 51 | } 52 | 53 | if (!$languages->contains($lang)) { 54 | $languages->push($lang); 55 | } 56 | 57 | foreach ($items as $key => $value) { 58 | $rawValues->push([ 59 | 'group' => $group, 60 | 'key' => $key, 61 | 'lang' => $lang, 62 | 'value' => $value, 63 | ]); 64 | } 65 | } 66 | 67 | //creating the csv file 68 | /** @var Collection $data */ 69 | $data = collect([]); 70 | 71 | foreach ($rawValues as $rawValue) { 72 | $index = $data->search(fn ($item) => $item['group'] === $rawValue['group'] && $item['key'] === $rawValue['key']); 73 | 74 | if ($index === false) { 75 | $output = [ 76 | 'group' => $rawValue['group'], 77 | 'key' => $rawValue['key'], 78 | ]; 79 | 80 | foreach ($languages as $lang) { 81 | $real = $rawValue['lang'] === $lang ? $rawValue['value'] : ''; 82 | $output[$lang] = $real; 83 | } 84 | 85 | $data->push($output); 86 | } else { 87 | foreach ($languages as $lang) { 88 | $code = $rawValue['lang'] === $lang ? $rawValue['value'] : null; 89 | 90 | if ($code !== null) { 91 | $new = $data->get($index); 92 | $new[$lang] = $rawValue['value']; 93 | $data->put($index, $new); 94 | } 95 | } 96 | } 97 | } 98 | 99 | return $data->values(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Importers/JsonLanguagesImporter.php: -------------------------------------------------------------------------------- 1 | option('include'))->explode(',')->reject(fn ($i) => empty($i)); 29 | $exclude = Str::of($command->option('exclude'))->explode(',')->reject(fn ($i) => empty($i)); 30 | 31 | /** @var Collection $languages */ 32 | $languages = collect([]); 33 | 34 | /** @var Collection $rawValues */ 35 | $rawValues = collect([]); 36 | 37 | //get all files 38 | $files = File::glob(lang_path('*.json')); 39 | 40 | foreach ($files as $file) { 41 | $items = json_decode(File::get($file), true, 512, JSON_THROW_ON_ERROR); 42 | $lang = pathinfo($file, PATHINFO_FILENAME); 43 | 44 | if ($include->isNotEmpty() && !$include->contains($lang)) { 45 | continue; 46 | } 47 | 48 | if ($exclude->isNotEmpty() && $exclude->contains($lang)) { 49 | continue; 50 | } 51 | 52 | if (!$languages->contains($lang)) { 53 | $languages->push($lang); 54 | } 55 | 56 | foreach ($items as $keys => $value) { 57 | $composite = explode('.', $keys); 58 | $group = $composite[0]; 59 | $key = implode('.', array_slice($composite, 1)); 60 | 61 | $rawValues->push([ 62 | 'group' => $group, 63 | 'key' => $key, 64 | 'lang' => $lang, 65 | 'value' => $value, 66 | ]); 67 | } 68 | } 69 | 70 | //creating the csv file 71 | /** @var Collection $data */ 72 | $data = collect([]); 73 | 74 | foreach ($rawValues as $rawValue) { 75 | $index = $data->search(fn ($item) => $item['group'] === $rawValue['group'] && $item['key'] === $rawValue['key']); 76 | 77 | if ($index === false) { 78 | $output = [ 79 | 'group' => $rawValue['group'], 80 | 'key' => $rawValue['key'], 81 | ]; 82 | 83 | foreach ($languages as $lang) { 84 | $real = $rawValue['lang'] === $lang ? $rawValue['value'] : ''; 85 | $output[$lang] = $real; 86 | } 87 | 88 | $data->push($output); 89 | } else { 90 | foreach ($languages as $lang) { 91 | $code = $rawValue['lang'] === $lang ? $rawValue['value'] : null; 92 | 93 | if ($code !== null) { 94 | $new = $data->get($index); 95 | $new[$lang] = $rawValue['value']; 96 | $data->put($index, $new); 97 | } 98 | } 99 | } 100 | } 101 | 102 | return $data->values(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Importers/LaravelImporter.php: -------------------------------------------------------------------------------- 1 | option('include'))->explode(',')->reject(fn ($i) => empty($i)); 29 | $exclude = Str::of($command->option('exclude'))->explode(',')->reject(fn ($i) => empty($i)); 30 | 31 | /** @var Collection $languages */ 32 | $languages = collect([]); 33 | 34 | /** @var Collection $rawValues */ 35 | $rawValues = collect([]); 36 | 37 | //get all files 38 | $files = File::glob(lang_path('**/*.php')); 39 | 40 | foreach ($files as $file) { 41 | $items = include $file; 42 | $group = pathinfo($file, PATHINFO_FILENAME); 43 | $lang = str_replace('_', '-', basename(dirname($file))); 44 | 45 | if ($include->isNotEmpty() && !$include->contains($lang)) { 46 | continue; 47 | } 48 | 49 | if ($exclude->isNotEmpty() && $exclude->contains($lang)) { 50 | continue; 51 | } 52 | 53 | if (!$languages->contains($lang)) { 54 | $languages->push($lang); 55 | } 56 | 57 | //loop through array recursive 58 | $iterator = new RecursiveIteratorIterator( 59 | new RecursiveArrayIterator($items), 60 | RecursiveIteratorIterator::SELF_FIRST 61 | ); 62 | 63 | $path = []; 64 | foreach ($iterator as $key => $value) { 65 | $path[$iterator->getDepth()] = $key; 66 | if (!is_array($value)) { 67 | $rawValues->push([ 68 | 'group' => $group, 69 | 'key' => implode('.', array_slice($path, 0, $iterator->getDepth() + 1)), 70 | 'lang' => $lang, 71 | 'value' => $value, 72 | ]); 73 | } 74 | } 75 | } 76 | 77 | //creating the csv file 78 | /** @var Collection $data */ 79 | $data = collect([]); 80 | 81 | foreach ($rawValues as $rawValue) { 82 | $index = $data->search(function ($item) use ($rawValue) { 83 | return $item['group'] === $rawValue['group'] && $item['key'] === $rawValue['key']; 84 | }); 85 | 86 | if ($index === false) { 87 | $output = [ 88 | 'group' => $rawValue['group'], 89 | 'key' => $rawValue['key'], 90 | ]; 91 | 92 | foreach ($languages as $lang) { 93 | $real = $rawValue['lang'] === $lang ? $rawValue['value'] : ''; 94 | $output[$lang] = $real; 95 | } 96 | 97 | $data->push($output); 98 | } else { 99 | foreach ($languages as $lang) { 100 | $code = $rawValue['lang'] === $lang ? $rawValue['value'] : null; 101 | 102 | if ($code !== null) { 103 | $new = $data->get($index); 104 | $new[$lang] = $rawValue['value']; 105 | $data->put($index, $new); 106 | } 107 | } 108 | } 109 | } 110 | 111 | return $data->values(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/LarexServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerPublishables(); 27 | } 28 | 29 | /** 30 | * Register services. 31 | * 32 | * @return void 33 | */ 34 | public function register(): void 35 | { 36 | $this->mergeConfigFrom(__DIR__.'/config/larex.php', 'larex'); 37 | 38 | $this->registerCommands(); 39 | } 40 | 41 | protected function registerCommands(): void 42 | { 43 | $this->commands([ 44 | LarexInitCommand::class, 45 | LarexExportCommand::class, 46 | LarexImportCommand::class, 47 | LarexSortCommand::class, 48 | LarexInsertCommand::class, 49 | LarexLintCommand::class, 50 | LarexLocalizeCommand::class, 51 | LarexFindCommand::class, 52 | LarexRemoveCommand::class, 53 | LarexLangAddCommand::class, 54 | LarexLangRemoveCommand::class, 55 | LarexLangOrderCommand::class, 56 | ]); 57 | } 58 | 59 | protected function registerPublishables(): void 60 | { 61 | $this->publishes([ 62 | __DIR__.'/config/larex.php' => config_path('larex.php'), 63 | ], 'larex-config'); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Linters/ConcurrentKeyLinter.php: -------------------------------------------------------------------------------- 1 | getRows()->each(function ($columns, $line) use (&$count) { 27 | $line += 2; 28 | 29 | if (!array_key_exists($columns['group'], $count)) { 30 | $count[$columns['group']] = []; 31 | } 32 | 33 | $this->setKeyPositions($columns['key'], $columns['key'], $line, $count[$columns['group']]); 34 | }); 35 | 36 | //get raw errors 37 | $rawErrors = []; 38 | collect($count)->each(function ($keys, $group) use (&$rawErrors) { 39 | $this->parseErrors($group, $keys, $rawErrors); 40 | }); 41 | 42 | //build errors 43 | $errors = []; 44 | collect($rawErrors)->each(function ($keys, $group) use (&$errors) { 45 | foreach ($keys as $key) { 46 | $error = []; 47 | $this->buildErrors($key, $error); 48 | $errors[$group][] = $error; 49 | } 50 | }); 51 | 52 | //print errors 53 | $messages = []; 54 | foreach ($errors as $group => $keys) { 55 | foreach ($keys as $subkeys) { 56 | $text = 'rows '; 57 | $errorKeys = collect($subkeys)->sort(); 58 | $i = 0; 59 | foreach ($errorKeys as $key => $row) { 60 | $text .= "$row ($group.$key)"; 61 | 62 | if ($i < $errorKeys->count() - 1) { 63 | $text .= ', '; 64 | } 65 | $i++; 66 | } 67 | $text .= ';'; 68 | $messages[] = $text; 69 | } 70 | } 71 | 72 | if (count($messages) > 0) { 73 | throw new LintException('Concurrent keys found:', $messages); 74 | } 75 | } 76 | 77 | protected function setKeyPositions($originalKey, $currentKey, $n, &$array): void 78 | { 79 | $keys = collect(explode('.', $currentKey)); 80 | 81 | $firstKey = $keys->first(); 82 | if (!array_key_exists($firstKey, $array)) { 83 | $key = $currentKey === $firstKey ? $originalKey : null; 84 | $row = $currentKey === $firstKey ? $n : null; 85 | $array[$firstKey] = ['key' => $key, 'row' => $row, 'children' => []]; 86 | } elseif (array_key_exists($originalKey, $array)) { 87 | $array[$firstKey]['key'] = $originalKey; 88 | $array[$firstKey]['row'] = $n; 89 | } 90 | 91 | $otherKeys = $keys->skip(1); 92 | if ($otherKeys->isNotEmpty()) { 93 | $this->setKeyPositions($originalKey, $otherKeys->implode('.'), $n, $array[$firstKey]['children']); 94 | } 95 | } 96 | 97 | protected function parseErrors(string $group, array $keys, &$errors): void 98 | { 99 | foreach ($keys as $key) { 100 | if (count($key['children']) === 0) { 101 | continue; 102 | } 103 | 104 | if ($key['row'] !== null) { 105 | $errors[$group][] = $key; 106 | continue; 107 | } 108 | 109 | $this->parseErrors($group, $key['children'], $errors); 110 | } 111 | } 112 | 113 | protected function buildErrors(array $key, &$error): void 114 | { 115 | if ($key['row'] !== null) { 116 | $error[$key['key']] = $key['row']; 117 | } 118 | 119 | foreach ($key['children'] as $child) { 120 | $this->buildErrors($child, $error); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Linters/DuplicateKeyLinter.php: -------------------------------------------------------------------------------- 1 | getRows()->each(function ($columns, $line) use (&$count) { 27 | $line += 2; 28 | $count["{$columns['group']}.{$columns['key']}"][] = $line; 29 | }); 30 | 31 | $duplicates = collect($count)->filter(fn ($items) => count($items) > 1); 32 | 33 | if ($duplicates->isNotEmpty()) { 34 | $errors = collect([]); 35 | 36 | foreach ($duplicates as $duplicate => $positions) { 37 | $message = ''; 38 | foreach ($positions as $p => $position) { 39 | $message .= $position; 40 | 41 | if ($p < count($positions) - 1) { 42 | $message .= ', '; 43 | } 44 | } 45 | $message .= " ($duplicate)"; 46 | $errors->push($message); 47 | } 48 | 49 | $subject = Str::plural('key', $duplicates->count()); 50 | throw new LintException("{$duplicates->count()} duplicate $subject found:", $errors->toArray()); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Linters/DuplicateValueLinter.php: -------------------------------------------------------------------------------- 1 | getRows()->each(function ($columns, $line) use ($errors) { 29 | $line += 2; 30 | 31 | $duplicates = Utils::getDuplicateValues($columns->skip(2)); 32 | 33 | if (count($duplicates) > 0) { 34 | $message = "row $line ({$columns['group']}.{$columns['key']}), columns: "; 35 | 36 | foreach ($duplicates as $positions) { 37 | foreach ($positions as $p => $lang) { 38 | $column = array_search($lang, array_keys($columns->toArray()), true) + 1; 39 | $message .= "$column ($lang)"; 40 | 41 | if ($p < count($positions) - 1) { 42 | $message .= ', '; 43 | } 44 | } 45 | } 46 | 47 | $errors->push($message); 48 | } 49 | }); 50 | 51 | if ($errors->isNotEmpty()) { 52 | $subject = Str::plural('value', $errors->count()); 53 | throw new LintException("{$errors->count()} duplicate $subject found:", $errors->toArray()); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Linters/NoValueLinter.php: -------------------------------------------------------------------------------- 1 | getRows()->each(function ($columns, $line) use ($errors) { 28 | $line += 2; 29 | 30 | $columns->skip(2)->each(function ($value, $lang) use ($columns, $line, $errors) { 31 | if ($value === '') { 32 | $column = array_search($lang, array_keys($columns->toArray()), true) + 1; 33 | $errors->push("row $line ({$columns['group']}.{$columns['key']}), column $column ($lang)"); 34 | } 35 | }); 36 | }); 37 | 38 | if ($errors->isNotEmpty()) { 39 | $subject = Str::plural('value', $errors->count()); 40 | throw new LintException("{$errors->count()} missing $subject found:", $errors->toArray()); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Linters/SameParametersLinter.php: -------------------------------------------------------------------------------- 1 | getHeader()->flip(); 29 | 30 | //check if all parameters are the same 31 | $reader->getRows()->each(function (Collection $columns, int $line) use ($headers, &$errors) { 32 | $line += 2; 33 | 34 | $group = $columns->get('group'); 35 | $key = $columns->get('key'); 36 | 37 | //get parameters for every language 38 | $parameters = $columns 39 | ->skip(2) 40 | ->map(function (string $item) { 41 | preg_match_all('/:\w+/', $item, $matches); 42 | 43 | return collect($matches[0] ?? []); 44 | }); 45 | 46 | //get first item with max parameters count 47 | /** @var Collection $max */ 48 | $max = $parameters->sortByDesc(fn ($item) => count($item))->first(); 49 | 50 | //check if all parameters are the same 51 | foreach ($parameters as $lang => $items) { 52 | $column = $headers->get($lang) + 1; 53 | $value = $columns->get($lang); 54 | 55 | if (blank($value) && config('larex.ignore_empty_values', false)) { 56 | continue; 57 | } 58 | 59 | $diff = $max 60 | ->diff($items) 61 | ->unique(); //TODO: Temporary fix. We need to investigate why there are duplicates. 62 | 63 | foreach ($diff as $param) { 64 | $errors->push("line $line ($group.$key), column $column ($lang): missing $param parameter"); 65 | } 66 | } 67 | }); 68 | 69 | if ($errors->isNotEmpty()) { 70 | throw new LintException('Missing parameters found:', $errors->toArray()); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Linters/UntranslatedStringsLinter.php: -------------------------------------------------------------------------------- 1 | getRows() 31 | ->map(fn ($item) => "{$item['group']}.{$item['key']}") 32 | ->collect() 33 | ->values(); 34 | 35 | $stringsUntranslated = $stringsFound 36 | ->reject(fn ($item) => $stringsSaved->contains($item['string'])) 37 | ->groupBy('filename') 38 | ->map->sortBy('line') 39 | ->map->values() 40 | ->flatten(1); 41 | 42 | if ($stringsUntranslated->isNotEmpty()) { 43 | $errors = collect([]); 44 | 45 | foreach ($stringsUntranslated as $item) { 46 | $errors->push("{$item['string']} is untranslated at line {$item['line']}, column {$item['column']} in {$item['filepath']}"); 47 | } 48 | 49 | $subject = Str::plural('string', $errors->count()); 50 | throw new LintException("{$errors->count()} untranslated $subject found:", $errors->toArray()); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Linters/UnusedStringsLinter.php: -------------------------------------------------------------------------------- 1 | pluck('string'); 28 | 29 | $stringsUnused = $reader 30 | ->getRows() 31 | ->reject(fn ($item) => $stringsFound->contains("{$item['group']}.{$item['key']}")) 32 | ->collect(); 33 | 34 | if ($stringsUnused->isNotEmpty()) { 35 | $errors = collect([]); 36 | 37 | foreach ($stringsUnused as $i => $item) { 38 | $line = $i + 2; 39 | $errors->push("{$item['group']}.{$item['key']} is unused at line $line"); 40 | } 41 | 42 | $subject = Str::plural('string', $errors->count()); 43 | throw new LintException("{$errors->count()} unused $subject found:", $errors->toArray()); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Linters/ValidHeaderLinter.php: -------------------------------------------------------------------------------- 1 | getHeader(); 25 | 26 | if (!isset($header[0])) { 27 | throw new LintException('First header column is missing.'); 28 | } 29 | 30 | if ($header[0] !== 'group') { 31 | throw new LintException('First header column value is invalid. Must be "group".'); 32 | } 33 | 34 | if (!isset($header[1])) { 35 | throw new LintException('Second header column is missing.'); 36 | } 37 | 38 | if ($header[1] !== 'key') { 39 | throw new LintException('Second header column value is invalid. Must be "key".'); 40 | } 41 | 42 | if (count($header) <= 2) { 43 | throw new LintException('No language columns found.'); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Linters/ValidHtmlValueLinter.php: -------------------------------------------------------------------------------- 1 | getRows()->each(function ($columns, $line) use ($errors) { 29 | $line += 2; 30 | 31 | $columns->skip(2)->each(function ($value, $lang) use ($columns, $line, $errors) { 32 | if (!Utils::isValidHTML($value)) { 33 | $column = array_search($lang, array_keys($columns->toArray()), true) + 1; 34 | $errors->push("line $line ({$columns['group']}.{$columns['key']}), column: $column ($lang)"); 35 | } 36 | }); 37 | }); 38 | 39 | if ($errors->isNotEmpty()) { 40 | $subject = Str::plural('string', $errors->count()); 41 | throw new LintException("{$errors->count()} invalid html $subject found:", $errors->toArray()); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Linters/ValidLanguageCodeLinter.php: -------------------------------------------------------------------------------- 1 | getHeader()->skip(2)->each(function ($code, $n) { 26 | $suggest = Utils::isValidLanguageCode($code); 27 | 28 | if ($suggest !== true) { 29 | $column = $n + 1; 30 | $suggest = is_string($suggest) ? " Did you mean: $suggest" : ''; 31 | throw new LintException("Language code not valid in column $column ($code).".$suggest); 32 | } 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Stubs/default.stub: -------------------------------------------------------------------------------- 1 | group,key,en 2 | -------------------------------------------------------------------------------- /src/Support/CsvParser.php: -------------------------------------------------------------------------------- 1 | reader = $reader; 20 | $this->warning = []; 21 | $this->handleSubKeys = true; 22 | } 23 | 24 | public static function create(CsvReader $reader): self 25 | { 26 | return new self($reader); 27 | } 28 | 29 | public function setHandleSubKey(bool $value): self 30 | { 31 | $this->handleSubKeys = $value; 32 | 33 | return $this; 34 | } 35 | 36 | public function getWarnings(): array 37 | { 38 | return $this->warning; 39 | } 40 | 41 | public function parse(bool $skipEmpty = true): array 42 | { 43 | $languages = []; 44 | $header = $this->reader->getHeader(); 45 | $rows = $this->reader->getRows()->collect(); 46 | 47 | $this->validateRaw(); 48 | 49 | //loop rows 50 | foreach ($rows as $columns) { 51 | //get first two columns values 52 | $group = trim($columns->get('group')); 53 | $key = trim($columns->get('key')); 54 | 55 | //check if key is filled 56 | if ($key === '') { 57 | continue; 58 | } 59 | 60 | //loop language columns 61 | foreach ($columns->skip(2) as $lang => $value) { 62 | $lang = trim($lang); 63 | $item = $value ?? ''; 64 | 65 | if ($item === '') { 66 | if ($skipEmpty) { 67 | continue; 68 | } 69 | 70 | if (!array_key_exists($lang, $languages)) { 71 | $languages[$lang] = []; 72 | } 73 | 74 | continue; 75 | } 76 | 77 | if ($this->handleSubKeys) { 78 | Arr::set($languages[$lang][$group], $key, $item); 79 | } else { 80 | $languages[$lang][$group][$key] = $item; 81 | } 82 | } 83 | } 84 | 85 | //sort languages by column order 86 | return collect($languages) 87 | ->sortBy(fn ($item, $key) => array_search($key, $header->skip(2)->toArray(), true)) 88 | ->toArray(); 89 | } 90 | 91 | public function validateRaw(): void 92 | { 93 | //read raw csv 94 | /** @var Collection> $output */ 95 | $output = collect([]); 96 | $file = fopen($this->reader->getPath(), 'rb'); 97 | while (($cols = fgetcsv($file)) !== false) { 98 | /** @var Collection $columns */ 99 | $columns = collect($cols ?? []); 100 | $output->push($columns); 101 | } 102 | fclose($file); 103 | 104 | //loop collection 105 | foreach ($output->skip(1) as $i => $columns) { 106 | $line = $i + 1; 107 | 108 | //check if row is blank 109 | if ($columns->count() <= 1 && $columns->get(0) === null) { 110 | $this->warning[] = sprintf('Invalid row at line %d. The row will be skipped.', $line); 111 | continue; 112 | } 113 | 114 | //get first two columns values 115 | $group = $columns->get(0); 116 | $key = $columns->get(1); 117 | 118 | //check if key is filled 119 | if ($key === '') { 120 | $this->warning[] = sprintf('Missing key name at line %d. The row will be skipped.', $line); 121 | continue; 122 | } 123 | 124 | //loop language columns 125 | foreach ($columns->skip(2) as $j => $value) { 126 | $item = $value ?? ''; 127 | $column = $j + 1; 128 | 129 | if ($item === '') { 130 | $this->warning[] = sprintf( 131 | '%s.%s at line %d, column %d (%s) is missing. It will be skipped.', 132 | $group, $key, $line, $column, $output->first()->get($j) 133 | ); 134 | } 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/Support/CsvReader.php: -------------------------------------------------------------------------------- 1 | reader = SimpleExcelReader::create($path); 16 | } 17 | 18 | public static function create(string $path): self 19 | { 20 | return new self($path); 21 | } 22 | 23 | /** 24 | * @return Collection 25 | */ 26 | public function getHeader(): Collection 27 | { 28 | $header = $this->reader->getHeaders(); 29 | 30 | return is_array($header) ? collect($header) : collect([]); 31 | } 32 | 33 | public function getRows(): LazyCollection 34 | { 35 | return $this->reader->getRows()->mapInto(Collection::class); 36 | } 37 | 38 | public function getReader(): SimpleExcelReader 39 | { 40 | return $this->reader; 41 | } 42 | 43 | public function getPath(): string 44 | { 45 | return $this->reader->getPath(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Support/CsvWriter.php: -------------------------------------------------------------------------------- 1 | path = $path; 29 | $this->delimiter = ','; 30 | $this->enclosure = '"'; 31 | $this->escape = '"'; 32 | $this->filePointer = fopen($this->path, 'wb+'); 33 | } 34 | 35 | public static function create(string $path): self 36 | { 37 | return new self($path); 38 | } 39 | 40 | public function getPath(): string 41 | { 42 | return $this->path; 43 | } 44 | 45 | public function getNumberOfRows(): int 46 | { 47 | return $this->numberOfRows; 48 | } 49 | 50 | public function noHeaderRow(): self 51 | { 52 | $this->processHeader = false; 53 | 54 | return $this; 55 | } 56 | 57 | public function useDelimiter(string $delimiter): self 58 | { 59 | $this->delimiter = $delimiter; 60 | 61 | return $this; 62 | } 63 | 64 | public function useEnclosure(string $enclosure): self 65 | { 66 | $this->enclosure = $enclosure; 67 | 68 | return $this; 69 | } 70 | 71 | public function useEscape(string $escape): self 72 | { 73 | $this->escape = $escape; 74 | 75 | return $this; 76 | } 77 | 78 | public function addRow(array $row): self 79 | { 80 | if ($this->processHeader && $this->processingFirstRow) { 81 | $this->writeHeaderFromRow($row); 82 | } 83 | 84 | $wasWriteSuccessful = Utils::fputcsv($this->filePointer, array_values($row), $this->delimiter, $this->enclosure, $this->escape); 85 | if ($wasWriteSuccessful === false) { 86 | $this->closeAndClean(); 87 | throw new RuntimeException('Unable to write data'); 88 | } 89 | 90 | $this->numberOfRows++; 91 | 92 | $this->processingFirstRow = false; 93 | 94 | return $this; 95 | } 96 | 97 | public function addRows(array $rows): self 98 | { 99 | foreach ($rows as $row) { 100 | if (!is_array($row)) { 101 | $this->closeAndClean(); 102 | throw new InvalidArgumentException('The input should be an array of array'); 103 | } 104 | 105 | $this->addRow($row); 106 | } 107 | 108 | return $this; 109 | } 110 | 111 | public function whenAddRow(bool $condition, array $rowTrue, ?array $rowFalse = null): self 112 | { 113 | if ($condition) { 114 | $this->addRow($rowTrue); 115 | } elseif (is_array($rowFalse)) { 116 | $this->addRow($rowFalse); 117 | } 118 | 119 | return $this; 120 | } 121 | 122 | protected function writeHeaderFromRow(array $row): void 123 | { 124 | $headerValues = array_keys($row); 125 | 126 | $wasWriteSuccessful = Utils::fputcsv($this->filePointer, $headerValues, $this->delimiter, $this->enclosure, $this->escape); 127 | if ($wasWriteSuccessful === false) { 128 | $this->closeAndClean(); 129 | throw new RuntimeException('Unable to write data'); 130 | } 131 | 132 | $this->numberOfRows++; 133 | } 134 | 135 | protected function close(): void 136 | { 137 | if (is_resource($this->filePointer)) { 138 | fclose($this->filePointer); 139 | } 140 | } 141 | 142 | protected function closeAndClean(): void 143 | { 144 | $this->close(); 145 | 146 | if (file_exists($this->path) && is_file($this->path)) { 147 | unlink($this->path); 148 | } 149 | } 150 | 151 | public function __destruct() 152 | { 153 | $this->close(); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | replace(base_path(), '') 22 | ->replace('lang\\', 'lang/') 23 | ->ltrim('\\/'); 24 | } 25 | -------------------------------------------------------------------------------- /tests/Console/LarexExportTest.php: -------------------------------------------------------------------------------- 1 | artisan(LarexExportCommand::class) 8 | ->expectsOutput(sprintf("Processing the '%s' file...", csv_path(true))) 9 | ->expectsOutput(sprintf("The '%s' does not exists.", csv_path(true))) 10 | ->expectsOutput('Please create it with: php artisan larex:init or php artisan larex:import') 11 | ->assertExitCode(1); 12 | }); 13 | 14 | it('does not export strings due to both filled --include and --exclude options', function () { 15 | $this->artisan(LarexInitCommand::class)->run(); 16 | 17 | $this->artisan(LarexExportCommand::class, ['--include' => '', '--exclude' => '']) 18 | ->expectsOutput(sprintf("Processing the '%s' file...", csv_path(true))) 19 | ->expectsOutput('The --include and --exclude options can be used only one at a time.') 20 | ->assertExitCode(1); 21 | }); 22 | 23 | it('does not export strings due to missing exporter', function () { 24 | $this->artisan(LarexInitCommand::class); 25 | 26 | $this->artisan(LarexExportCommand::class, ['exporter' => 'foo']) 27 | ->expectsOutput("Exporter 'foo' not found.") 28 | ->expectsOutput('') 29 | ->expectsOutput('Available exporters:') 30 | ->expectsOutput('laravel - Export data from CSV to Laravel localization files') 31 | ->expectsOutput('json:lang - Export data from CSV to JSON by language') 32 | ->expectsOutput('json:group - Export data from CSV to JSON by group') 33 | ->expectsOutput('') 34 | ->assertExitCode(1); 35 | }); 36 | 37 | it('does not export strings due to invalid exporter', function () { 38 | config([ 39 | 'larex.exporters.list.foo' => new class() { 40 | }, 41 | ]); 42 | 43 | $this->artisan(LarexInitCommand::class); 44 | 45 | $this->artisan(LarexExportCommand::class, ['exporter' => 'foo']) 46 | ->expectsOutput("Exporter 'foo' must implements Lukasss93\Larex\Contracts\Exporter interface.") 47 | ->assertExitCode(1); 48 | }); 49 | -------------------------------------------------------------------------------- /tests/Console/LarexFindTest.php: -------------------------------------------------------------------------------- 1 | artisan(LarexFindCommand::class, ['value' => 'test']) 8 | ->expectsOutput(sprintf("The '%s' does not exists.", csv_path(true))) 9 | ->expectsOutput('Please create it with: php artisan larex:init or php artisan larex:import') 10 | ->assertExitCode(1); 11 | }); 12 | 13 | it('finds no strings', function () { 14 | initFromStub('console.find.input'); 15 | 16 | $this->artisan(LarexFindCommand::class, ['value' => 'test']) 17 | ->expectsOutput('No string found.') 18 | ->assertExitCode(0); 19 | }); 20 | 21 | it('finds strings', function () { 22 | initFromStub('console.find.input'); 23 | 24 | $this->artisan(LarexFindCommand::class, ['value' => 'app']) 25 | ->expectsTable(['group', 'key', 'en'], [ 26 | ['app', 'car', 'Car'], 27 | ['app', 'apple', 'Apple'], 28 | ]) 29 | ->assertExitCode(0); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/Console/LarexInitTest.php: -------------------------------------------------------------------------------- 1 | artisan(LarexInitCommand::class) 8 | ->expectsOutput(sprintf('%s created successfully.', csv_path(true))) 9 | ->assertExitCode(0); 10 | 11 | expect(csv_path()) 12 | ->toBeFile() 13 | ->fileContent() 14 | ->toEqual(Utils::getStub('default')); 15 | }); 16 | 17 | it('initializes localization file with --base option', function () { 18 | $this->artisan(LarexInitCommand::class, ['--base' => true]) 19 | ->expectsOutput(sprintf('%s created successfully.', csv_path(true))) 20 | ->assertExitCode(0); 21 | 22 | expect(csv_path()) 23 | ->toBeFile() 24 | ->fileContent() 25 | ->toEqual(Utils::getStub('base')); 26 | }); 27 | 28 | it('does not initialize localization file due to file already exists', function () { 29 | $this->artisan(LarexInitCommand::class)->run(); 30 | 31 | $this->artisan(LarexInitCommand::class) 32 | ->expectsOutput(sprintf('%s already exists.', csv_path(true))) 33 | ->assertExitCode(1); 34 | 35 | expect(csv_path()) 36 | ->toBeFile(); 37 | }); 38 | -------------------------------------------------------------------------------- /tests/Console/LarexLangAddTest.php: -------------------------------------------------------------------------------- 1 | artisan(LarexLangAddCommand::class, ['code' => 'xyz']) 9 | ->expectsOutput('Invalid language code (xyz)') 10 | ->assertExitCode(1); 11 | 12 | expect(csv_path()) 13 | ->toBeFile() 14 | ->fileContent() 15 | ->toEqualStub('console.lang-add.lang-input'); 16 | }); 17 | 18 | it('does not add a new language due to invalid language code + suggest', function () { 19 | initFromStub('console.lang-add.lang-input'); 20 | 21 | $this->artisan(LarexLangAddCommand::class, ['code' => 'itx']) 22 | ->expectsOutput('Language code is not valid (itx). Did you mean: it?') 23 | ->assertExitCode(1); 24 | 25 | expect(csv_path()) 26 | ->toBeFile() 27 | ->fileContent() 28 | ->toEqualStub('console.lang-add.lang-input'); 29 | }); 30 | 31 | it('adds a new language column', function () { 32 | initFromStub('console.lang-add.lang-input'); 33 | 34 | $this->artisan(LarexLangAddCommand::class, ['code' => 'es']) 35 | ->expectsOutput('Added language column: "es"') 36 | ->assertExitCode(0); 37 | 38 | expect(csv_path()) 39 | ->toBeFile() 40 | ->fileContent() 41 | ->toEqualStub('console.lang-add.lang-add-output'); 42 | }); 43 | -------------------------------------------------------------------------------- /tests/Console/LarexLangOrderTest.php: -------------------------------------------------------------------------------- 1 | artisan(LarexLangOrderCommand::class, ['from' => 'it', 'to' => 'en']) 7 | ->expectsOutput(sprintf("The '%s' does not exists.", csv_path(true))) 8 | ->expectsOutput('Please create it with: php artisan larex:init') 9 | ->assertExitCode(1); 10 | }); 11 | 12 | test('source language: wrong code', function () { 13 | initFromStub('console.lang-order.input'); 14 | 15 | $this->artisan(LarexLangOrderCommand::class, ['from' => 'de', 'to' => 'en']) 16 | ->expectsOutput('The source language (de) is not valid.') 17 | ->assertExitCode(1); 18 | }); 19 | 20 | test('source language: wrong position', function () { 21 | initFromStub('console.lang-order.input'); 22 | 23 | $this->artisan(LarexLangOrderCommand::class, ['from' => '4', 'to' => 'en']) 24 | ->expectsOutput('The source language (4) is not valid.') 25 | ->assertExitCode(1); 26 | }); 27 | 28 | test('destination language: wrong code', function () { 29 | initFromStub('console.lang-order.input'); 30 | 31 | $this->artisan(LarexLangOrderCommand::class, ['from' => 'it', 'to' => 'de']) 32 | ->expectsOutput('The destination language (de) is not valid.') 33 | ->assertExitCode(1); 34 | }); 35 | 36 | test('destination language: wrong position', function () { 37 | initFromStub('console.lang-order.input'); 38 | 39 | $this->artisan(LarexLangOrderCommand::class, ['from' => 'it', 'to' => '4']) 40 | ->expectsOutput('The destination language (4) is not valid.') 41 | ->assertExitCode(1); 42 | }); 43 | 44 | test('same source and destination language', function ($from, $to) { 45 | initFromStub('console.lang-order.input'); 46 | 47 | $this->artisan(LarexLangOrderCommand::class, ['from' => 'it', 'to' => 'it']) 48 | ->expectsOutput('The source and destination languages are the same.') 49 | ->assertExitCode(1); 50 | })->with([ 51 | ['it', 'it'], 52 | ['it', '2'], 53 | ['2', 'it'], 54 | ['1', '1'], 55 | ]); 56 | 57 | test('order', function ($from, $to) { 58 | initFromStub('console.lang-order.input'); 59 | 60 | $this->artisan(LarexLangOrderCommand::class, ['from' => $from, 'to' => $to]) 61 | ->expectsOutput('Done.') 62 | ->assertExitCode(0); 63 | 64 | expect(csv_path()) 65 | ->toBeFile() 66 | ->fileContent() 67 | ->toEqualStub('console.lang-order.output'); 68 | })->with([ 69 | 'code' => ['it', 'en'], 70 | 'position' => ['2', '1'], 71 | ]); 72 | -------------------------------------------------------------------------------- /tests/Console/LarexLangRemoveTest.php: -------------------------------------------------------------------------------- 1 | artisan(LarexLangRemoveCommand::class, ['code' => 'es']) 9 | ->expectsOutput('The language code "es" is not present in the CSV file.') 10 | ->assertExitCode(1); 11 | 12 | expect(csv_path()) 13 | ->toBeFile() 14 | ->fileContent() 15 | ->toEqualStub('console.lang-remove.lang-input'); 16 | }); 17 | 18 | it('remove a language column', function () { 19 | initFromStub('console.lang-remove.lang-input'); 20 | 21 | $this->artisan(LarexLangRemoveCommand::class, ['code' => 'en']) 22 | ->expectsOutput('The language code "en" has been removed from the CSV file.') 23 | ->assertExitCode(0); 24 | 25 | expect(csv_path()) 26 | ->toBeFile() 27 | ->fileContent() 28 | ->toEqualStub('console.lang-remove.lang-remove-output'); 29 | }); 30 | -------------------------------------------------------------------------------- /tests/Console/LarexLintTest.php: -------------------------------------------------------------------------------- 1 | []]); 9 | 10 | $this->artisan(LarexLintCommand::class) 11 | ->expectsOutput(sprintf("The '%s' does not exists.", csv_path(true))) 12 | ->expectsOutput('Please create it with: php artisan larex:init') 13 | ->assertExitCode(1); 14 | }); 15 | 16 | it('does not lint due to missing linters', function () { 17 | config(['larex.linters' => []]); 18 | initFromStub('console.lint.no-linters'); 19 | 20 | $this->artisan(LarexLintCommand::class) 21 | ->expectsOutput('No linters executed!') 22 | ->assertExitCode(1); 23 | }); 24 | 25 | it('lints with failure', function () { 26 | config([ 27 | 'larex.linters' => [ 28 | DuplicateKeyLinter::class, 29 | ], 30 | ]); 31 | initFromStub('console.lint.failure'); 32 | 33 | $this->artisan(LarexLintCommand::class) 34 | ->expectsOutput(' FAIL 1 duplicate key found:') 35 | ->expectsOutput('└ 2, 3 (app.a)') 36 | ->expectsOutput('FAILURES!') 37 | ->expectsOutput('Linters: 1, Failures: 1') 38 | ->assertExitCode(1); 39 | }); 40 | 41 | it('lints successfully', function () { 42 | config([ 43 | 'larex.linters' => [ 44 | ValidHeaderLinter::class, 45 | ], 46 | ]); 47 | initFromStub('console.lint.success'); 48 | 49 | $this->artisan(LarexLintCommand::class) 50 | ->expectsOutput('OK (1 linter)') 51 | ->assertExitCode(0); 52 | }); 53 | -------------------------------------------------------------------------------- /tests/Console/LarexLocalizeTest.php: -------------------------------------------------------------------------------- 1 | artisan(LarexLocalizeCommand::class) 10 | ->expectsOutput('No unlocalized strings found.') 11 | ->assertExitCode(0); 12 | }); 13 | 14 | it('finds unlocalized strings', function () { 15 | $testFilePath = initFromStub('console.localize.with-strings.blade', resource_path('views/test.blade.php')); 16 | initFromStub('console.localize.with-strings.csv'); 17 | 18 | $this->artisan(LarexLocalizeCommand::class) 19 | ->expectsOutput('1 unlocalized string found:') 20 | ->expectsOutput(sprintf('app.news is untranslated at line 90, column 63 in %s', $testFilePath)) 21 | ->assertExitCode(0); 22 | }); 23 | 24 | it('finds unlocalized strings and import data', function () { 25 | $testFilePath = initFromStub('console.localize.with-strings-import.blade', resource_path('views/test.blade.php')); 26 | initFromStub('console.localize.with-strings-import.csv-before'); 27 | 28 | $this->artisan(LarexLocalizeCommand::class, ['--import' => true]) 29 | ->expectsOutput('1 unlocalized string found:') 30 | ->expectsOutput("app.news is untranslated at line 90, column 63 in $testFilePath") 31 | ->expectsOutput('') 32 | ->expectsOutput('Adding unlocalized string to CSV file...') 33 | ->expectsOutput('Done.') 34 | ->assertExitCode(0); 35 | 36 | expect(csv_path()) 37 | ->toBeFile() 38 | ->fileContent() 39 | ->toEqualStub('console.localize.with-strings-import.csv-after'); 40 | }); 41 | -------------------------------------------------------------------------------- /tests/Console/LarexRemoveTest.php: -------------------------------------------------------------------------------- 1 | artisan(LarexRemoveCommand::class, ['key' => 'app.car']) 9 | ->expectsOutput('1 string found to remove:') 10 | ->expectsOutput('└ app.car') 11 | ->expectsQuestion('Are you sure you want to delete 1 string?', true) 12 | ->expectsOutput('Removed successfully.') 13 | ->assertExitCode(0); 14 | 15 | expect(csv_path()) 16 | ->toBeFile() 17 | ->fileContent() 18 | ->toEqualStub('console.remove.base.output'); 19 | }); 20 | 21 | it('removes rows with --force option', function () { 22 | initFromStub('console.remove.base.input'); 23 | 24 | $this->artisan(LarexRemoveCommand::class, ['key' => 'app.car', '--force' => true]) 25 | ->expectsOutput('1 string found to remove:') 26 | ->expectsOutput('└ app.car') 27 | ->expectsOutput('Removed successfully.') 28 | ->assertExitCode(0); 29 | 30 | expect(csv_path()) 31 | ->toBeFile() 32 | ->fileContent() 33 | ->toEqualStub('console.remove.base.output'); 34 | }); 35 | 36 | it('does not remove rows due to user abort', function () { 37 | initFromStub('console.remove.abort.input'); 38 | 39 | $this->artisan(LarexRemoveCommand::class, ['key' => 'app.car']) 40 | ->expectsOutput('1 string found to remove:') 41 | ->expectsOutput('└ app.car') 42 | ->expectsQuestion('Are you sure you want to delete 1 string?', false) 43 | ->expectsOutput('Aborted.') 44 | ->assertExitCode(0); 45 | 46 | expect(csv_path()) 47 | ->toBeFile() 48 | ->fileContent() 49 | ->toEqualStub('console.remove.abort.output'); 50 | }); 51 | 52 | it('does not remove rows due to no strings found', function () { 53 | initFromStub('console.remove.nostrings.input'); 54 | 55 | $this->artisan(LarexRemoveCommand::class, ['key' => 'app.foo']) 56 | ->expectsOutput('No strings found to remove.') 57 | ->assertExitCode(0); 58 | 59 | expect(csv_path()) 60 | ->toBeFile() 61 | ->fileContent() 62 | ->toEqualStub('console.remove.nostrings.output'); 63 | }); 64 | 65 | it('removes rows with wildcard', function () { 66 | initFromStub('console.remove.wildcard.input'); 67 | 68 | $this->artisan(LarexRemoveCommand::class, ['key' => 'app.car*']) 69 | ->expectsOutput('2 strings found to remove:') 70 | ->expectsOutput('├ app.car') 71 | ->expectsOutput('└ app.carrier') 72 | ->expectsQuestion('Are you sure you want to delete 2 strings?', true) 73 | ->expectsOutput('Removed successfully.') 74 | ->assertExitCode(0); 75 | 76 | expect(csv_path()) 77 | ->toBeFile() 78 | ->fileContent() 79 | ->toEqualStub('console.remove.wildcard.output'); 80 | }); 81 | 82 | it('does not remove rows due to missing localization file', function () { 83 | $this->artisan(LarexRemoveCommand::class, ['key' => 'app.car']) 84 | ->expectsOutput(sprintf("The '%s' does not exists.", csv_path(true))) 85 | ->expectsOutput('Please create it with: php artisan larex:init') 86 | ->assertExitCode(1); 87 | 88 | expect(csv_path())->not->toBeFile(); 89 | }); 90 | 91 | it('removes rows and export data', function () { 92 | initFromStub('console.remove.export.input'); 93 | 94 | $this->artisan(LarexRemoveCommand::class, ['key' => 'app.car', '--export' => true]) 95 | ->expectsOutput('1 string found to remove:') 96 | ->expectsOutput('└ app.car') 97 | ->expectsQuestion('Are you sure you want to delete 1 string?', true) 98 | ->expectsOutput('Removed successfully.') 99 | ->expectsOutput(sprintf("Processing the '%s' file...", csv_path(true))) 100 | ->expectsOutput(sprintf('%s created successfully.', lang_rpath('en/app.php'))) 101 | ->expectsOutput(sprintf('%s created successfully.', lang_rpath('it/app.php'))) 102 | ->assertExitCode(0); 103 | 104 | expect(csv_path()) 105 | ->toBeFile() 106 | ->fileContent() 107 | ->toEqualStub('console.remove.export.output') 108 | //check exported en file 109 | ->and(lang_path('en/app.php')) 110 | ->toBeFile() 111 | ->fileContent() 112 | ->toEqualStub('console.remove.export.output-app-en') 113 | //check exported it file 114 | ->and(lang_path('it/app.php')) 115 | ->toBeFile() 116 | ->fileContent() 117 | ->toEqualStub('console.remove.export.output-app-it'); 118 | }); 119 | -------------------------------------------------------------------------------- /tests/Console/LarexSortTest.php: -------------------------------------------------------------------------------- 1 | artisan(LarexSortCommand::class) 9 | ->expectsOutput('Sorting che CSV rows...') 10 | ->expectsOutput('Sorting completed.') 11 | ->assertExitCode(0); 12 | 13 | expect(csv_path()) 14 | ->toBeFile() 15 | ->fileContent() 16 | ->toEqualStub('console.sort.sort-output'); 17 | }); 18 | 19 | it('does not sort rows due to missing localization file', function () { 20 | $this->artisan(LarexSortCommand::class) 21 | ->expectsOutput('Sorting che CSV rows...') 22 | ->expectsOutput(sprintf("The '%s' does not exists.", csv_path(true))) 23 | ->expectsOutput('Please create it with: php artisan larex:init') 24 | ->assertExitCode(1); 25 | 26 | expect(csv_path())->not->toBeFile(); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/Importers/JsonGroupsImporterTest.php: -------------------------------------------------------------------------------- 1 | artisan(LarexImportCommand::class, ['importer' => 'json:group']) 16 | ->expectsOutput('Importing entries...') 17 | ->expectsOutput('Data imported successfully.') 18 | ->assertExitCode(0); 19 | 20 | expect(csv_path()) 21 | ->toBeFile() 22 | ->fileContent() 23 | ->toEqualStub('importers.json-groups.base.output'); 24 | }); 25 | 26 | it('imports strings with --include option', function () { 27 | File::makeDirectory(lang_path('en'), 0755, true, true); 28 | File::makeDirectory(lang_path('fr'), 0755, true, true); 29 | File::makeDirectory(lang_path('it'), 0755, true, true); 30 | 31 | initFromStub('importers.json-groups.include-exclude.input-en', lang_path('en/app.json')); 32 | initFromStub('importers.json-groups.include-exclude.input-fr', lang_path('fr/app.json')); 33 | initFromStub('importers.json-groups.include-exclude.input-it', lang_path('it/app.json')); 34 | 35 | $this->artisan(LarexImportCommand::class, ['importer' => 'json:group', '--include' => 'en,fr']) 36 | ->expectsOutput('Importing entries...') 37 | ->expectsOutput('Data imported successfully.') 38 | ->assertExitCode(0); 39 | 40 | expect(csv_path()) 41 | ->toBeFile() 42 | ->fileContent() 43 | ->toEqualStub('importers.json-groups.include-exclude.include'); 44 | }); 45 | 46 | it('imports strings with --exclude option', function () { 47 | File::makeDirectory(lang_path('en'), 0755, true, true); 48 | File::makeDirectory(lang_path('fr'), 0755, true, true); 49 | File::makeDirectory(lang_path('it'), 0755, true, true); 50 | 51 | initFromStub('importers.json-groups.include-exclude.input-en', lang_path('en/app.json')); 52 | initFromStub('importers.json-groups.include-exclude.input-fr', lang_path('fr/app.json')); 53 | initFromStub('importers.json-groups.include-exclude.input-it', lang_path('it/app.json')); 54 | 55 | $this->artisan(LarexImportCommand::class, ['importer' => 'json:group', '--exclude' => 'fr']) 56 | ->expectsOutput('Importing entries...') 57 | ->expectsOutput('Data imported successfully.') 58 | ->assertExitCode(0); 59 | 60 | expect(csv_path()) 61 | ->toBeFile() 62 | ->fileContent() 63 | ->toEqualStub('importers.json-groups.include-exclude.exclude'); 64 | }); 65 | -------------------------------------------------------------------------------- /tests/Importers/JsonLanguagesImporterTest.php: -------------------------------------------------------------------------------- 1 | artisan(LarexImportCommand::class, ['importer' => 'json:lang']) 14 | ->expectsOutput('Importing entries...') 15 | ->expectsOutput('Data imported successfully.') 16 | ->assertExitCode(0); 17 | 18 | expect(csv_path()) 19 | ->toBeFile() 20 | ->fileContent() 21 | ->toEqualStub('importers.json-langs.base.output'); 22 | }); 23 | 24 | it('imports strings with --include option', function () { 25 | File::makeDirectory(lang_path('en'), 0755, true, true); 26 | File::makeDirectory(lang_path('fr'), 0755, true, true); 27 | File::makeDirectory(lang_path('it'), 0755, true, true); 28 | 29 | initFromStub('importers.json-langs.include-exclude.input-en', lang_path('en.json')); 30 | initFromStub('importers.json-langs.include-exclude.input-fr', lang_path('fr.json')); 31 | initFromStub('importers.json-langs.include-exclude.input-it', lang_path('it.json')); 32 | 33 | $this->artisan(LarexImportCommand::class, ['importer' => 'json:lang', '--include' => 'en,fr']) 34 | ->expectsOutput('Importing entries...') 35 | ->expectsOutput('Data imported successfully.') 36 | ->assertExitCode(0); 37 | 38 | expect(csv_path()) 39 | ->toBeFile() 40 | ->fileContent() 41 | ->toEqualStub('importers.json-langs.include-exclude.include'); 42 | }); 43 | 44 | it('imports strings with --exclude option', function () { 45 | File::makeDirectory(lang_path('en'), 0755, true, true); 46 | File::makeDirectory(lang_path('fr'), 0755, true, true); 47 | File::makeDirectory(lang_path('it'), 0755, true, true); 48 | 49 | initFromStub('importers.json-langs.include-exclude.input-en', lang_path('en.json')); 50 | initFromStub('importers.json-langs.include-exclude.input-fr', lang_path('fr.json')); 51 | initFromStub('importers.json-langs.include-exclude.input-it', lang_path('it.json')); 52 | 53 | $this->artisan(LarexImportCommand::class, ['importer' => 'json:lang', '--exclude' => 'fr']) 54 | ->expectsOutput('Importing entries...') 55 | ->expectsOutput('Data imported successfully.') 56 | ->assertExitCode(0); 57 | 58 | expect(csv_path()) 59 | ->toBeFile() 60 | ->fileContent() 61 | ->toEqualStub('importers.json-langs.include-exclude.exclude'); 62 | }); 63 | -------------------------------------------------------------------------------- /tests/Importers/LaravelImporterTest.php: -------------------------------------------------------------------------------- 1 | artisan(LarexImportCommand::class, ['importer' => 'laravel']) 16 | ->expectsOutput('Importing entries...') 17 | ->expectsOutput('Data imported successfully.') 18 | ->assertExitCode(0); 19 | 20 | expect(csv_path()) 21 | ->toBeFile() 22 | ->fileContent() 23 | ->toEqualStub('importers.laravel.base.output'); 24 | }); 25 | 26 | it('imports strings with --include option', function () { 27 | File::makeDirectory(lang_path('en'), 0755, true, true); 28 | File::makeDirectory(lang_path('fr'), 0755, true, true); 29 | File::makeDirectory(lang_path('it'), 0755, true, true); 30 | 31 | initFromStub('importers.laravel.include-exclude.input-en', lang_path('en/app.php')); 32 | initFromStub('importers.laravel.include-exclude.input-fr', lang_path('fr/app.php')); 33 | initFromStub('importers.laravel.include-exclude.input-it', lang_path('it/app.php')); 34 | 35 | $this->artisan(LarexImportCommand::class, ['importer' => 'laravel', '--include' => 'en,fr']) 36 | ->expectsOutput('Importing entries...') 37 | ->expectsOutput('Data imported successfully.') 38 | ->assertExitCode(0); 39 | 40 | expect(csv_path()) 41 | ->toBeFile() 42 | ->fileContent() 43 | ->toEqualStub('importers.laravel.include-exclude.include'); 44 | }); 45 | 46 | it('imports strings with --exclude option', function () { 47 | File::makeDirectory(lang_path('en'), 0755, true, true); 48 | File::makeDirectory(lang_path('fr'), 0755, true, true); 49 | File::makeDirectory(lang_path('it'), 0755, true, true); 50 | 51 | initFromStub('importers.laravel.include-exclude.input-en', lang_path('en/app.php')); 52 | initFromStub('importers.laravel.include-exclude.input-fr', lang_path('fr/app.php')); 53 | initFromStub('importers.laravel.include-exclude.input-it', lang_path('it/app.php')); 54 | 55 | $this->artisan(LarexImportCommand::class, ['importer' => 'laravel', '--exclude' => 'fr']) 56 | ->expectsOutput('Importing entries...') 57 | ->expectsOutput('Data imported successfully.') 58 | ->assertExitCode(0); 59 | 60 | expect(csv_path()) 61 | ->toBeFile() 62 | ->fileContent() 63 | ->toEqualStub('importers.laravel.include-exclude.exclude'); 64 | }); 65 | 66 | it('imports strings with territory', function () { 67 | File::makeDirectory(lang_path('en_GB'), 0755, true, true); 68 | File::makeDirectory(lang_path('it'), 0755, true, true); 69 | 70 | initFromStub('importers.laravel.territory.input-en_GB-complex', lang_path('en_GB/complex.php')); 71 | initFromStub('importers.laravel.territory.input-en_GB-simple', lang_path('en_GB/simple.php')); 72 | initFromStub('importers.laravel.territory.input-it-complex', lang_path('it/complex.php')); 73 | initFromStub('importers.laravel.territory.input-it-simple', lang_path('it/simple.php')); 74 | 75 | $this->artisan(LarexImportCommand::class, ['importer' => 'laravel']) 76 | ->expectsOutput('Importing entries...') 77 | ->expectsOutput('Data imported successfully.') 78 | ->assertExitCode(0); 79 | 80 | expect(csv_path()) 81 | ->toBeFile() 82 | ->fileContent() 83 | ->toEqualStub('importers.laravel.territory.output'); 84 | }); 85 | 86 | it('imports strings and set the source language', 87 | function (string $source, string $expected, bool $skipSourceReordering) { 88 | File::makeDirectory(lang_path('ar'), 0755, true, true); 89 | File::makeDirectory(lang_path('en'), 0755, true, true); 90 | File::makeDirectory(lang_path('it'), 0755, true, true); 91 | 92 | initFromStub('importers.laravel.source.input-ar', lang_path('ar/app.php')); 93 | initFromStub('importers.laravel.source.input-en', lang_path('en/app.php')); 94 | initFromStub('importers.laravel.source.input-it', lang_path('it/app.php')); 95 | 96 | config(['larex.source_language' => $source]); 97 | 98 | $this->artisan(LarexImportCommand::class, 99 | ['importer' => 'laravel', '--skip-source-reordering' => $skipSourceReordering]) 100 | ->expectsOutput('Importing entries...') 101 | ->expectsOutput('Data imported successfully.') 102 | ->assertExitCode(0); 103 | 104 | expect(csv_path()) 105 | ->toBeFile() 106 | ->fileContent() 107 | ->toEqualStub($expected); 108 | })->with([ 109 | 'ar' => ['ar', 'importers.laravel.source.output-ar', false], 110 | 'en' => ['en', 'importers.laravel.source.output-en', false], 111 | 'en-skip' => ['en', 'importers.laravel.source.output-ar', true], 112 | 'invalid-lang' => ['es', 'importers.laravel.source.output-ar', false], 113 | ]); 114 | -------------------------------------------------------------------------------- /tests/Linters/ConcurrentKeyLinterTest.php: -------------------------------------------------------------------------------- 1 | [ 9 | ConcurrentKeyLinter::class, 10 | ], 11 | ]); 12 | 13 | initFromStub('linters.concurrent-key.success'); 14 | 15 | $this->artisan(LarexLintCommand::class) 16 | ->expectsOutput('OK (1 linter)') 17 | ->assertExitCode(0); 18 | }); 19 | 20 | it('fails', function () { 21 | config([ 22 | 'larex.linters' => [ 23 | ConcurrentKeyLinter::class, 24 | ], 25 | ]); 26 | 27 | initFromStub('linters.concurrent-key.failure'); 28 | 29 | $this->artisan(LarexLintCommand::class) 30 | ->expectsOutput(' FAIL Concurrent keys found:') 31 | ->expectsOutput('├ rows 2 (add.a.b), 3 (add.a.b.c), 4 (add.a);') 32 | ->expectsOutput('├ rows 8 (add.d), 9 (add.d.a);') 33 | ->expectsOutput('├ rows 10 (app.e.a), 11 (app.e);') 34 | ->expectsOutput('├ rows 12 (app.f), 13 (app.f.a), 14 (app.f.b);') 35 | ->expectsOutput('└ rows 15 (app.g.a), 17 (app.g.a.b);') 36 | ->expectsOutput('FAILURES!') 37 | ->expectsOutput('Linters: 1, Failures: 1') 38 | ->assertExitCode(1); 39 | }); 40 | -------------------------------------------------------------------------------- /tests/Linters/DuplicateKeyLinterTest.php: -------------------------------------------------------------------------------- 1 | [ 9 | DuplicateKeyLinter::class, 10 | ], 11 | ]); 12 | 13 | initFromStub('linters.duplicate-key.success'); 14 | 15 | $this->artisan(LarexLintCommand::class) 16 | ->expectsOutput('OK (1 linter)') 17 | ->assertExitCode(0); 18 | }); 19 | 20 | it('fails', function () { 21 | config([ 22 | 'larex.linters' => [ 23 | DuplicateKeyLinter::class, 24 | ], 25 | ]); 26 | 27 | initFromStub('linters.duplicate-key.failure'); 28 | 29 | $this->artisan(LarexLintCommand::class) 30 | ->expectsOutput(' FAIL 1 duplicate key found:') 31 | ->expectsOutput('└ 2, 3 (app.apple)') 32 | ->expectsOutput('FAILURES!') 33 | ->expectsOutput('Linters: 1, Failures: 1') 34 | ->assertExitCode(1); 35 | }); 36 | -------------------------------------------------------------------------------- /tests/Linters/DuplicateValueLinterTest.php: -------------------------------------------------------------------------------- 1 | [ 9 | DuplicateValueLinter::class, 10 | ], 11 | ]); 12 | 13 | initFromStub('linters.duplicate-value.success'); 14 | 15 | $this->artisan(LarexLintCommand::class) 16 | ->expectsOutput('OK (1 linter)') 17 | ->assertExitCode(0); 18 | }); 19 | 20 | it('fails', function () { 21 | config([ 22 | 'larex.linters' => [ 23 | DuplicateValueLinter::class, 24 | ], 25 | ]); 26 | 27 | initFromStub('linters.duplicate-value.failure'); 28 | 29 | $this->artisan(LarexLintCommand::class) 30 | ->expectsOutput(' FAIL 1 duplicate value found:') 31 | ->expectsOutput('└ row 3 (app.ark), columns: 3 (en), 4 (it)') 32 | ->expectsOutput('FAILURES!') 33 | ->expectsOutput('Linters: 1, Failures: 1') 34 | ->assertExitCode(1); 35 | }); 36 | -------------------------------------------------------------------------------- /tests/Linters/NoValueLinterTest.php: -------------------------------------------------------------------------------- 1 | [ 9 | NoValueLinter::class, 10 | ], 11 | ]); 12 | 13 | initFromStub('linters.no-value.success'); 14 | 15 | $this->artisan(LarexLintCommand::class) 16 | ->expectsOutput('OK (1 linter)') 17 | ->assertExitCode(0); 18 | }); 19 | 20 | it('fails', function () { 21 | config([ 22 | 'larex.linters' => [ 23 | NoValueLinter::class, 24 | ], 25 | ]); 26 | 27 | initFromStub('linters.no-value.failure'); 28 | 29 | $this->artisan(LarexLintCommand::class) 30 | ->expectsOutput(' FAIL 4 missing values found:') 31 | ->expectsOutput('├ row 2 (app.a), column 3 (en)') 32 | ->expectsOutput('├ row 3 (app.b), column 4 (it)') 33 | ->expectsOutput('├ row 4 (app.c), column 3 (en)') 34 | ->expectsOutput('└ row 4 (app.c), column 4 (it)') 35 | ->expectsOutput('FAILURES!') 36 | ->expectsOutput('Linters: 1, Failures: 1') 37 | ->assertExitCode(1); 38 | }); 39 | -------------------------------------------------------------------------------- /tests/Linters/SameParametersLinterTest.php: -------------------------------------------------------------------------------- 1 | [SameParametersLinter::class], 9 | ]); 10 | 11 | initFromStub('linters.same-parameters.success'); 12 | 13 | $this->artisan(LarexLintCommand::class) 14 | ->expectsOutput('OK (1 linter)') 15 | ->assertExitCode(0); 16 | }); 17 | 18 | it('fails', function () { 19 | config([ 20 | 'larex.linters' => [SameParametersLinter::class], 21 | ]); 22 | 23 | initFromStub('linters.same-parameters.failure'); 24 | 25 | $this->artisan(LarexLintCommand::class) 26 | ->expectsOutput(' FAIL Missing parameters found:') 27 | ->expectsOutput('├ line 2 (app.hello), column 4 (it): missing :age parameter') 28 | ->expectsOutput('├ line 2 (app.hello), column 5 (es): missing :name parameter') 29 | ->expectsOutput('├ line 3 (app.cat), column 3 (en): missing :thing parameter') 30 | ->expectsOutput('├ line 3 (app.cat), column 5 (es): missing :animal parameter') 31 | ->expectsOutput('├ line 3 (app.cat), column 5 (es): missing :thing parameter') 32 | ->expectsOutput('└ line 6 (app.bye), column 5 (es): missing :name parameter') 33 | ->expectsOutput('FAILURES!') 34 | ->expectsOutput('Linters: 1, Failures: 1') 35 | ->assertExitCode(1); 36 | }); 37 | 38 | it('fails ignoring empty values', function () { 39 | config([ 40 | 'larex.linters' => [SameParametersLinter::class], 41 | 'larex.ignore_empty_values' => true, 42 | ]); 43 | 44 | initFromStub('linters.same-parameters.failure'); 45 | 46 | $this->artisan(LarexLintCommand::class) 47 | ->expectsOutput(' FAIL Missing parameters found:') 48 | ->expectsOutput('├ line 2 (app.hello), column 4 (it): missing :age parameter') 49 | ->expectsOutput('├ line 2 (app.hello), column 5 (es): missing :name parameter') 50 | ->expectsOutput('├ line 3 (app.cat), column 3 (en): missing :thing parameter') 51 | ->expectsOutput('├ line 3 (app.cat), column 5 (es): missing :animal parameter') 52 | ->expectsOutput('└ line 3 (app.cat), column 5 (es): missing :thing parameter') 53 | ->expectsOutput('FAILURES!') 54 | ->expectsOutput('Linters: 1, Failures: 1') 55 | ->assertExitCode(1); 56 | }); 57 | -------------------------------------------------------------------------------- /tests/Linters/UntranslatedStringsLinterTest.php: -------------------------------------------------------------------------------- 1 | [ 9 | UntranslatedStringsLinter::class, 10 | ], 11 | ]); 12 | 13 | initFromStub('linters.untranslated-strings.blade', resource_path('views/test.blade.php')); 14 | initFromStub('linters.untranslated-strings.success'); 15 | 16 | $this->artisan(LarexLintCommand::class) 17 | ->expectsOutput('OK (1 linter)') 18 | ->assertExitCode(0); 19 | }); 20 | 21 | it('fails', function () { 22 | config([ 23 | 'larex.linters' => [ 24 | UntranslatedStringsLinter::class, 25 | ], 26 | ]); 27 | 28 | $testFilePath = initFromStub('linters.untranslated-strings.blade', resource_path('views/test.blade.php')); 29 | initFromStub('linters.untranslated-strings.failure'); 30 | 31 | $this->artisan(LarexLintCommand::class) 32 | ->expectsOutput(' FAIL 1 untranslated string found:') 33 | ->expectsOutput('└ app.news is untranslated at line 90, column 63 in '.$testFilePath) 34 | ->expectsOutput('FAILURES!') 35 | ->expectsOutput('Linters: 1, Failures: 1') 36 | ->assertExitCode(1); 37 | }); 38 | -------------------------------------------------------------------------------- /tests/Linters/UnusedStringsLinterTest.php: -------------------------------------------------------------------------------- 1 | [ 9 | UnusedStringsLinter::class, 10 | ], 11 | ]); 12 | 13 | initFromStub('linters.untranslated-strings.blade', resource_path('views/test.blade.php')); 14 | initFromStub('linters.unused-strings.success'); 15 | 16 | $this->artisan(LarexLintCommand::class) 17 | ->expectsOutput('OK (1 linter)') 18 | ->assertExitCode(0); 19 | }); 20 | 21 | it('fails', function () { 22 | config([ 23 | 'larex.linters' => [ 24 | UnusedStringsLinter::class, 25 | ], 26 | ]); 27 | 28 | initFromStub('linters.untranslated-strings.blade', resource_path('views/test.blade.php')); 29 | initFromStub('linters.unused-strings.failure'); 30 | 31 | $this->artisan(LarexLintCommand::class) 32 | ->expectsOutput(' FAIL 1 unused string found:') 33 | ->expectsOutput('└ app.apple is unused at line 4') 34 | ->expectsOutput('FAILURES!') 35 | ->expectsOutput('Linters: 1, Failures: 1') 36 | ->assertExitCode(1); 37 | }); 38 | -------------------------------------------------------------------------------- /tests/Linters/ValidHeaderLinterTest.php: -------------------------------------------------------------------------------- 1 | [ 9 | ValidHeaderLinter::class, 10 | ], 11 | ]); 12 | 13 | initFromStub('linters.valid-header.success'); 14 | 15 | $this->artisan(LarexLintCommand::class) 16 | ->expectsOutput('OK (1 linter)') 17 | ->assertExitCode(0); 18 | }); 19 | 20 | it('fails due to missing first column', function () { 21 | config([ 22 | 'larex.linters' => [ 23 | ValidHeaderLinter::class, 24 | ], 25 | ]); 26 | 27 | initFromStub('linters.valid-header.missing-first-column'); 28 | 29 | $this->artisan(LarexLintCommand::class) 30 | ->expectsOutput(' FAIL First header column is missing.') 31 | ->expectsOutput('FAILURES!') 32 | ->expectsOutput('Linters: 1, Failures: 1') 33 | ->assertExitCode(1); 34 | }); 35 | 36 | it('fails due to invalid first column', function () { 37 | config([ 38 | 'larex.linters' => [ 39 | ValidHeaderLinter::class, 40 | ], 41 | ]); 42 | 43 | initFromStub('linters.valid-header.invalid-first-column'); 44 | 45 | $this->artisan(LarexLintCommand::class) 46 | ->expectsOutput(' FAIL First header column value is invalid. Must be "group".') 47 | ->expectsOutput('FAILURES!') 48 | ->expectsOutput('Linters: 1, Failures: 1') 49 | ->assertExitCode(1); 50 | }); 51 | 52 | it('fails due to missing second column', function () { 53 | config([ 54 | 'larex.linters' => [ 55 | ValidHeaderLinter::class, 56 | ], 57 | ]); 58 | 59 | initFromStub('linters.valid-header.missing-second-column'); 60 | 61 | $this->artisan(LarexLintCommand::class) 62 | ->expectsOutput(' FAIL Second header column is missing.') 63 | ->expectsOutput('FAILURES!') 64 | ->expectsOutput('Linters: 1, Failures: 1') 65 | ->assertExitCode(1); 66 | }); 67 | 68 | it('fails due to invalid second column', function () { 69 | config([ 70 | 'larex.linters' => [ 71 | ValidHeaderLinter::class, 72 | ], 73 | ]); 74 | 75 | initFromStub('linters.valid-header.invalid-second-column'); 76 | 77 | $this->artisan(LarexLintCommand::class) 78 | ->expectsOutput(' FAIL Second header column value is invalid. Must be "key".') 79 | ->expectsOutput('FAILURES!') 80 | ->expectsOutput('Linters: 1, Failures: 1') 81 | ->assertExitCode(1); 82 | }); 83 | 84 | it('fails due to no language columns', function () { 85 | config([ 86 | 'larex.linters' => [ 87 | ValidHeaderLinter::class, 88 | ], 89 | ]); 90 | 91 | initFromStub('linters.valid-header.no-language-columns'); 92 | 93 | $this->artisan(LarexLintCommand::class) 94 | ->expectsOutput(' FAIL No language columns found.') 95 | ->expectsOutput('FAILURES!') 96 | ->expectsOutput('Linters: 1, Failures: 1') 97 | ->assertExitCode(1); 98 | }); 99 | -------------------------------------------------------------------------------- /tests/Linters/ValidHtmlValueLinterTest.php: -------------------------------------------------------------------------------- 1 | [ 9 | ValidHtmlValueLinter::class, 10 | ], 11 | ]); 12 | 13 | initFromStub('linters.valid-html-value.success'); 14 | 15 | $this->artisan(LarexLintCommand::class) 16 | ->expectsOutput('OK (1 linter)') 17 | ->assertExitCode(0); 18 | }); 19 | 20 | it('fails', function () { 21 | config([ 22 | 'larex.linters' => [ 23 | ValidHtmlValueLinter::class, 24 | ], 25 | ]); 26 | 27 | initFromStub('linters.valid-html-value.failure'); 28 | 29 | $this->artisan(LarexLintCommand::class) 30 | ->expectsOutput(' FAIL 2 invalid html strings found:') 31 | ->expectsOutput('├ line 2 (app.apple), column: 3 (en)') 32 | ->expectsOutput('└ line 3 (app.ark), column: 4 (it)') 33 | ->expectsOutput('FAILURES!') 34 | ->expectsOutput('Linters: 1, Failures: 1') 35 | ->assertExitCode(1); 36 | }); 37 | -------------------------------------------------------------------------------- /tests/Linters/ValidLanguageCodeLinterTest.php: -------------------------------------------------------------------------------- 1 | [ 9 | ValidLanguageCodeLinter::class, 10 | ], 11 | ]); 12 | 13 | initFromStub('linters.valid-language-code.success'); 14 | 15 | $this->artisan(LarexLintCommand::class) 16 | ->expectsOutput('OK (1 linter)') 17 | ->assertExitCode(0); 18 | }); 19 | 20 | it('fails', function () { 21 | config([ 22 | 'larex.linters' => [ 23 | ValidLanguageCodeLinter::class, 24 | ], 25 | ]); 26 | 27 | initFromStub('linters.valid-language-code.failure'); 28 | 29 | $this->artisan(LarexLintCommand::class) 30 | ->expectsOutput(' FAIL Language code not valid in column 3 (xxx).') 31 | ->expectsOutput('FAILURES!') 32 | ->expectsOutput('Linters: 1, Failures: 1') 33 | ->assertExitCode(1); 34 | }); 35 | 36 | it('fails and suggest', function () { 37 | config([ 38 | 'larex.linters' => [ 39 | ValidLanguageCodeLinter::class, 40 | ], 41 | ]); 42 | 43 | initFromStub('linters.valid-language-code.failure-and-suggest'); 44 | 45 | $this->artisan(LarexLintCommand::class) 46 | ->expectsOutput(' FAIL Language code not valid in column 3 (it-IF). Did you mean: it-IT') 47 | ->expectsOutput('FAILURES!') 48 | ->expectsOutput('Linters: 1, Failures: 1') 49 | ->assertExitCode(1); 50 | }); 51 | -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | beforeEach(function () { 20 | //set env to avoid --watch loop 21 | putenv('NOLOOP=1'); 22 | 23 | //set global csv settings 24 | config([ 25 | 'larex.csv' => [ 26 | 'path' => lang_path('localization.csv'), 27 | ], 28 | 'larex.search' => [ 29 | 'dirs' => ['resources/views'], 30 | 'patterns' => ['*.php'], 31 | 'functions' => ['__', 'trans', '@lang'], 32 | ], 33 | 'larex.eol' => "\n", 34 | ]); 35 | 36 | //clear lang folder 37 | $items = glob(lang_path('*')); 38 | foreach ($items as $item) { 39 | if (is_dir($item)) { 40 | File::deleteDirectory($item); 41 | } else { 42 | File::delete($item); 43 | } 44 | } 45 | 46 | //delete lang folder 47 | if (file_exists(lang_path())) { 48 | @rmdir(lang_path()); 49 | } 50 | }) 51 | ->in(__DIR__); 52 | 53 | /* 54 | |-------------------------------------------------------------------------- 55 | | Expectations 56 | |-------------------------------------------------------------------------- 57 | | 58 | | When you're writing tests, you often need to check that values meet certain conditions. The 59 | | "expect()" function gives you access to a set of "expectations" methods that you can use 60 | | to assert different things. Of course, you may extend the Expectation API at any time. 61 | | 62 | */ 63 | 64 | expect()->extend('fileContent', fn () => $this->and($this->value = File::get($this->value))); 65 | expect()->extend('toEqualStub', fn (string $name, $eol = "\n") => $this->toEqual(getTestStub($name, $eol))); 66 | 67 | /* 68 | |-------------------------------------------------------------------------- 69 | | Functions 70 | |-------------------------------------------------------------------------- 71 | | 72 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your 73 | | project that you don't want to repeat in every file. Here you can also expose helpers as 74 | | global functions to help you to reduce the number of lines of code in your test files. 75 | | 76 | */ 77 | 78 | function getTestStub(string $name, $eol = "\n"): string 79 | { 80 | $name = str_replace('.', '/', $name); 81 | $content = file_get_contents(__DIR__.'/Stubs/'.$name.'.stub'); 82 | 83 | return Utils::normalizeEOLs($content, $eol); 84 | } 85 | 86 | function initFromStub(string $stub, string $file = null): string 87 | { 88 | $filePath = Utils::normalizeDS($file ?? csv_path()); 89 | Utils::filePut($filePath, getTestStub($stub)); 90 | 91 | return $filePath; 92 | } 93 | -------------------------------------------------------------------------------- /tests/Stubs/console/find/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,car,Car,Auto 3 | app,apple,Apple,Mela 4 | check,money,Money,Soldi 5 | -------------------------------------------------------------------------------- /tests/Stubs/console/insert/base/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,standard,one,uno 3 | -------------------------------------------------------------------------------- /tests/Stubs/console/insert/base/output-app-en.stub: -------------------------------------------------------------------------------- 1 | 'one', 6 | 'uncle' => 'Uncle', 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /tests/Stubs/console/insert/base/output-app-it.stub: -------------------------------------------------------------------------------- 1 | 'uno', 6 | 'uncle' => 'Zio', 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /tests/Stubs/console/insert/base/output.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,standard,one,uno 3 | app,uncle,Uncle,Zio 4 | -------------------------------------------------------------------------------- /tests/Stubs/console/insert/correction/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,standard,one,uno 3 | -------------------------------------------------------------------------------- /tests/Stubs/console/insert/correction/output-app-en.stub: -------------------------------------------------------------------------------- 1 | 'one', 6 | 'dad' => 'Dad', 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /tests/Stubs/console/insert/correction/output-app-it.stub: -------------------------------------------------------------------------------- 1 | 'uno', 6 | 'dad' => 'Papà', 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /tests/Stubs/console/insert/correction/output.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,standard,one,uno 3 | app,dad,Dad,Papà 4 | -------------------------------------------------------------------------------- /tests/Stubs/console/insert/exists/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,standard,one,uno 3 | -------------------------------------------------------------------------------- /tests/Stubs/console/insert/exists/output-continue-no.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,standard,one,uno 3 | app,uncle,Uncle,Zio 4 | -------------------------------------------------------------------------------- /tests/Stubs/console/insert/exists/output-continue-yes.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,standard,one,uno 3 | app,standard,Uncle,Zio 4 | -------------------------------------------------------------------------------- /tests/Stubs/console/insert/init/output.stub: -------------------------------------------------------------------------------- 1 | group,key,en 2 | app,hello,Hello! 3 | -------------------------------------------------------------------------------- /tests/Stubs/console/insert/special/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,standard,one,uno 3 | -------------------------------------------------------------------------------- /tests/Stubs/console/insert/special/output.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,standard,one,uno 3 | app,special,àèìòù,"""'!£$%&/()=?*§<>;:_,.-#@ç[]{}°" 4 | -------------------------------------------------------------------------------- /tests/Stubs/console/lang-add/lang-add-output.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it,es 2 | common,apple,apple,mela, 3 | common,car,car,auto, 4 | common,bike,bike,bici, 5 | -------------------------------------------------------------------------------- /tests/Stubs/console/lang-add/lang-input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | common,apple,apple,mela 3 | common,car,car,auto 4 | common,bike,bike,bici 5 | -------------------------------------------------------------------------------- /tests/Stubs/console/lang-order/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it,es 2 | common,apple,apple,mela,manzana 3 | common,car,car,auto,coche 4 | common,bike,bike,bici,bicicleta 5 | -------------------------------------------------------------------------------- /tests/Stubs/console/lang-order/output.stub: -------------------------------------------------------------------------------- 1 | group,key,it,en,es 2 | common,apple,mela,apple,manzana 3 | common,car,auto,car,coche 4 | common,bike,bici,bike,bicicleta 5 | -------------------------------------------------------------------------------- /tests/Stubs/console/lang-remove/lang-input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | common,apple,apple,mela 3 | common,car,car,auto 4 | common,bike,bike,bici 5 | -------------------------------------------------------------------------------- /tests/Stubs/console/lang-remove/lang-remove-output.stub: -------------------------------------------------------------------------------- 1 | group,key,it 2 | common,apple,mela 3 | common,car,auto 4 | common,bike,bici 5 | -------------------------------------------------------------------------------- /tests/Stubs/console/lint/failure.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,a,filled,riempito 3 | app,a,car,macchina 4 | -------------------------------------------------------------------------------- /tests/Stubs/console/lint/no-linters.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,a,filled,riempito 3 | -------------------------------------------------------------------------------- /tests/Stubs/console/lint/success.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,fill,fill,riempi -------------------------------------------------------------------------------- /tests/Stubs/console/localize/no-strings/blade.stub: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Laravel 8 | 9 | 10 | 11 | 12 | 13 | 65 | 66 | 67 |
68 | @if (Route::has('login')) 69 | 80 | @endif 81 | 82 |
83 |
84 | Laravel 85 |
86 | 87 | 97 |
98 |
99 | 100 | 101 | -------------------------------------------------------------------------------- /tests/Stubs/console/localize/no-strings/csv.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,docs,Documentation,Documentazione 3 | app,news,News,Notizie -------------------------------------------------------------------------------- /tests/Stubs/console/localize/with-strings-import/blade.stub: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Laravel 8 | 9 | 10 | 11 | 12 | 13 | 65 | 66 | 67 |
68 | @if (Route::has('login')) 69 | 80 | @endif 81 | 82 |
83 |
84 | Laravel 85 |
86 | 87 | 97 |
98 |
99 | 100 | 101 | -------------------------------------------------------------------------------- /tests/Stubs/console/localize/with-strings-import/csv-after.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,docs,Documentation,Documentazione 3 | app,news,, 4 | -------------------------------------------------------------------------------- /tests/Stubs/console/localize/with-strings-import/csv-before.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,docs,Documentation,Documentazione -------------------------------------------------------------------------------- /tests/Stubs/console/localize/with-strings/blade.stub: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Laravel 8 | 9 | 10 | 11 | 12 | 13 | 65 | 66 | 67 |
68 | @if (Route::has('login')) 69 | 80 | @endif 81 | 82 |
83 |
84 | Laravel 85 |
86 | 87 | 97 |
98 |
99 | 100 | 101 | -------------------------------------------------------------------------------- /tests/Stubs/console/localize/with-strings/csv.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,docs,Documentation,Documentazione -------------------------------------------------------------------------------- /tests/Stubs/console/remove/abort/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,one,One,Uno 3 | app,car,Car,Macchina 4 | -------------------------------------------------------------------------------- /tests/Stubs/console/remove/abort/output.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,one,One,Uno 3 | app,car,Car,Macchina 4 | -------------------------------------------------------------------------------- /tests/Stubs/console/remove/base/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,one,One,Uno 3 | app,car,Car,Macchina 4 | -------------------------------------------------------------------------------- /tests/Stubs/console/remove/base/output.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,one,One,Uno 3 | -------------------------------------------------------------------------------- /tests/Stubs/console/remove/export/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,one,One,Uno 3 | app,car,Car,Macchina 4 | -------------------------------------------------------------------------------- /tests/Stubs/console/remove/export/output-app-en.stub: -------------------------------------------------------------------------------- 1 | 'One', 6 | 7 | ]; 8 | -------------------------------------------------------------------------------- /tests/Stubs/console/remove/export/output-app-it.stub: -------------------------------------------------------------------------------- 1 | 'Uno', 6 | 7 | ]; 8 | -------------------------------------------------------------------------------- /tests/Stubs/console/remove/export/output.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,one,One,Uno 3 | -------------------------------------------------------------------------------- /tests/Stubs/console/remove/nostrings/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,one,One,Uno 3 | app,car,Car,Macchina 4 | -------------------------------------------------------------------------------- /tests/Stubs/console/remove/nostrings/output.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,one,One,Uno 3 | app,car,Car,Macchina 4 | -------------------------------------------------------------------------------- /tests/Stubs/console/remove/wildcard/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,one,One,Uno 3 | app,car,Car,Macchina 4 | app,carrier,Carrier,Vettore 5 | -------------------------------------------------------------------------------- /tests/Stubs/console/remove/wildcard/output.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,one,One,Uno 3 | -------------------------------------------------------------------------------- /tests/Stubs/console/sort/sort-input.stub: -------------------------------------------------------------------------------- 1 | group,key,en 2 | bbb,abc,123 3 | aaa,cba,123 4 | aaa,bca,123 ok wow 5 | -------------------------------------------------------------------------------- /tests/Stubs/console/sort/sort-output.stub: -------------------------------------------------------------------------------- 1 | group,key,en 2 | aaa,bca,123 ok wow 3 | aaa,cba,123 4 | bbb,abc,123 5 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-groups/base/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,first,First,Primo 3 | app,second,Second,Secondo 4 | app,third,Third,Terzo 5 | special,multi.a,A,a 6 | special,multi.b,B,b 7 | special,empty.escape,nope,"" 8 | special,empty.noescape,nope, 9 | special,enclosure,nope,"è ""molto"" bello" 10 | special,numeric.1,January,Gennaio 11 | special,numeric.2,February,Febbraio 12 | special,numeric.3,March,Marzo 13 | special,space.escape,nope," " 14 | special,space.noescape,nope, 15 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-groups/base/output-en-app.stub: -------------------------------------------------------------------------------- 1 | { 2 | "first": "First", 3 | "second": "Second", 4 | "third": "Third" 5 | } 6 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-groups/base/output-en-special.stub: -------------------------------------------------------------------------------- 1 | { 2 | "multi.a": "A", 3 | "multi.b": "B", 4 | "empty.escape": "nope", 5 | "empty.noescape": "nope", 6 | "enclosure": "nope", 7 | "numeric.1": "January", 8 | "numeric.2": "February", 9 | "numeric.3": "March", 10 | "space.escape": "nope", 11 | "space.noescape": "nope" 12 | } 13 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-groups/base/output-it-app.stub: -------------------------------------------------------------------------------- 1 | { 2 | "first": "Primo", 3 | "second": "Secondo", 4 | "third": "Terzo" 5 | } 6 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-groups/base/output-it-special.stub: -------------------------------------------------------------------------------- 1 | { 2 | "multi.a": "a", 3 | "multi.b": "b", 4 | "enclosure": "è \"molto\" bello", 5 | "numeric.1": "Gennaio", 6 | "numeric.2": "Febbraio", 7 | "numeric.3": "Marzo", 8 | "space.escape": " ", 9 | "space.noescape": " " 10 | } 11 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-groups/spaces/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app , first , First , Primo 3 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-groups/spaces/output-en-app.stub: -------------------------------------------------------------------------------- 1 | { 2 | "first": " First " 3 | } 4 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-groups/spaces/output-it-app.stub: -------------------------------------------------------------------------------- 1 | { 2 | "first": " Primo " 3 | } 4 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-groups/warnings/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,first,First,Primo 3 | 4 | app, 5 | app,second, 6 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-groups/warnings/output-en-app.stub: -------------------------------------------------------------------------------- 1 | { 2 | "first": "First" 3 | } 4 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-groups/warnings/output-it-app.stub: -------------------------------------------------------------------------------- 1 | { 2 | "first": "Primo" 3 | } 4 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-language/base/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,first,First,Primo 3 | app,second,Second,Secondo 4 | another,third,Third,Terzo 5 | sub,multi.a,A,a 6 | sub,multi.b,B,b 7 | empty,escape.yes,nope,"" 8 | empty,escape.no,nope, 9 | special,enclosure,nope,"è ""molto"" bello" 10 | numeric,months.1,January,Gennaio 11 | numeric,months.2,February,Febbraio 12 | numeric,months.3,March,Marzo 13 | space,escape.yes,nope," " 14 | space,escape.no,nope, 15 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-language/base/output-en.stub: -------------------------------------------------------------------------------- 1 | { 2 | "app.first": "First", 3 | "app.second": "Second", 4 | "another.third": "Third", 5 | "sub.multi.a": "A", 6 | "sub.multi.b": "B", 7 | "empty.escape.yes": "nope", 8 | "empty.escape.no": "nope", 9 | "special.enclosure": "nope", 10 | "numeric.months.1": "January", 11 | "numeric.months.2": "February", 12 | "numeric.months.3": "March", 13 | "space.escape.yes": "nope", 14 | "space.escape.no": "nope" 15 | } 16 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-language/base/output-it.stub: -------------------------------------------------------------------------------- 1 | { 2 | "app.first": "Primo", 3 | "app.second": "Secondo", 4 | "another.third": "Terzo", 5 | "sub.multi.a": "a", 6 | "sub.multi.b": "b", 7 | "special.enclosure": "è \"molto\" bello", 8 | "numeric.months.1": "Gennaio", 9 | "numeric.months.2": "Febbraio", 10 | "numeric.months.3": "Marzo", 11 | "space.escape.yes": " ", 12 | "space.escape.no": " " 13 | } 14 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-language/include-exclude/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,first,First,Primo 3 | app,second,Second,Secondo 4 | another,third,Third,Terzo 5 | sub,multi.a,A,a 6 | sub,multi.b,B,b 7 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-language/include-exclude/output-en.stub: -------------------------------------------------------------------------------- 1 | { 2 | "app.first": "First", 3 | "app.second": "Second", 4 | "another.third": "Third", 5 | "sub.multi.a": "A", 6 | "sub.multi.b": "B" 7 | } 8 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-language/include-exclude/output-it.stub: -------------------------------------------------------------------------------- 1 | { 2 | "app.first": "Primo", 3 | "app.second": "Secondo", 4 | "another.third": "Terzo", 5 | "sub.multi.a": "a", 6 | "sub.multi.b": "b" 7 | } 8 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-language/spaces/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app , first , First , Primo 3 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-language/spaces/output-en.stub: -------------------------------------------------------------------------------- 1 | { 2 | "app.first": " First " 3 | } 4 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-language/spaces/output-it.stub: -------------------------------------------------------------------------------- 1 | { 2 | "app.first": " Primo " 3 | } 4 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-language/warnings/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | 3 | app, 4 | app,zero,,Zero 5 | app,first,First,Primo 6 | app,second,Second,Secondo 7 | another,third,Third,Terzo 8 | sub,multi.a,A,a 9 | sub,multi.b,B,b 10 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-language/warnings/output-en.stub: -------------------------------------------------------------------------------- 1 | { 2 | "app.first": "First", 3 | "app.second": "Second", 4 | "another.third": "Third", 5 | "sub.multi.a": "A", 6 | "sub.multi.b": "B" 7 | } 8 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/json-language/warnings/output-it.stub: -------------------------------------------------------------------------------- 1 | { 2 | "app.zero": "Zero", 3 | "app.first": "Primo", 4 | "app.second": "Secondo", 5 | "another.third": "Terzo", 6 | "sub.multi.a": "a", 7 | "sub.multi.b": "b" 8 | } 9 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/base/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,first,First,Primo 3 | app,second,Second,Secondo 4 | app,third,Third,Terzo 5 | special,multi.a,A,a 6 | special,multi.b,B,b 7 | special,empty.escape,nope,"" 8 | special,empty.noescape,nope, 9 | special,enclosure,nope,"è ""molto"" bello" 10 | special,numeric.1,January,Gennaio 11 | special,numeric.2,February,Febbraio 12 | special,numeric.3,March,Marzo 13 | special,space.escape,nope," " 14 | special,space.noescape,nope, 15 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/base/output-en-app.stub: -------------------------------------------------------------------------------- 1 | 'First', 6 | 'second' => 'Second', 7 | 'third' => 'Third', 8 | 9 | ]; 10 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/base/output-en-special.stub: -------------------------------------------------------------------------------- 1 | [ 6 | 'a' => 'A', 7 | 'b' => 'B', 8 | ], 9 | 'empty' => [ 10 | 'escape' => 'nope', 11 | 'noescape' => 'nope', 12 | ], 13 | 'enclosure' => 'nope', 14 | 'numeric' => [ 15 | 1 => 'January', 16 | 2 => 'February', 17 | 3 => 'March', 18 | ], 19 | 'space' => [ 20 | 'escape' => 'nope', 21 | 'noescape' => 'nope', 22 | ], 23 | 24 | ]; 25 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/base/output-it-app.stub: -------------------------------------------------------------------------------- 1 | 'Primo', 6 | 'second' => 'Secondo', 7 | 'third' => 'Terzo', 8 | 9 | ]; 10 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/base/output-it-special.stub: -------------------------------------------------------------------------------- 1 | [ 6 | 'a' => 'a', 7 | 'b' => 'b', 8 | ], 9 | 'enclosure' => 'è "molto" bello', 10 | 'numeric' => [ 11 | 1 => 'Gennaio', 12 | 2 => 'Febbraio', 13 | 3 => 'Marzo', 14 | ], 15 | 'space' => [ 16 | 'escape' => ' ', 17 | 'noescape' => ' ', 18 | ], 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/include-exclude/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,first,First,Primo 3 | app,second,Second,Secondo 4 | another,third,Third,Terzo 5 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/include-exclude/output-en-another.stub: -------------------------------------------------------------------------------- 1 | 'Third', 6 | 7 | ]; 8 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/include-exclude/output-en-app.stub: -------------------------------------------------------------------------------- 1 | 'First', 6 | 'second' => 'Second', 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/include-exclude/output-it-another.stub: -------------------------------------------------------------------------------- 1 | 'Terzo', 6 | 7 | ]; 8 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/include-exclude/output-it-app.stub: -------------------------------------------------------------------------------- 1 | 'Primo', 6 | 'second' => 'Secondo', 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/spaces/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app , first , First , Primo 3 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/spaces/output-en.stub: -------------------------------------------------------------------------------- 1 | ' First ', 6 | 7 | ]; 8 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/spaces/output-it.stub: -------------------------------------------------------------------------------- 1 | ' Primo ', 6 | 7 | ]; 8 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/territory/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en-GB,it 2 | app,first,First,Primo 3 | app,second,Second,Secondo 4 | app,third,Third,Terzo 5 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/territory/output-en_GB.stub: -------------------------------------------------------------------------------- 1 | 'First', 6 | 'second' => 'Second', 7 | 'third' => 'Third', 8 | 9 | ]; 10 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/territory/output-it.stub: -------------------------------------------------------------------------------- 1 | 'Primo', 6 | 'second' => 'Secondo', 7 | 'third' => 'Terzo', 8 | 9 | ]; 10 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/warnings/input.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,first,First,Primo 3 | 4 | app, 5 | app,second, 6 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/warnings/output-en.stub: -------------------------------------------------------------------------------- 1 | 'First', 6 | 7 | ]; 8 | -------------------------------------------------------------------------------- /tests/Stubs/exporters/laravel/warnings/output-it.stub: -------------------------------------------------------------------------------- 1 | 'Primo', 6 | 7 | ]; 8 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-groups/base/input-en-complex.stub: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "Hello", 3 | "this.car": "This car", 4 | "this.bus": "This bus", 5 | "very.long.limousine": "It's a very long limousine", 6 | "spaced key": "there is a column, separator in this string", 7 | "another": "different style", 8 | "ohno": "how \"it\" works now?" 9 | } 10 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-groups/base/input-en-simple.stub: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "Hello" 3 | } 4 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-groups/base/input-it-complex.stub: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "Ciao", 3 | "this.car": "Questa macchina", 4 | "this.bus": "Questo bus", 5 | "very.long.limousine": "È una limousine molto lunga" 6 | } 7 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-groups/base/input-it-simple.stub: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "Ciao", 3 | "bike": "Bicicletta" 4 | } 5 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-groups/base/output.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | complex,hello,Hello,Ciao 3 | complex,this.car,This car,Questa macchina 4 | complex,this.bus,This bus,Questo bus 5 | complex,very.long.limousine,It's a very long limousine,È una limousine molto lunga 6 | complex,spaced key,"there is a column, separator in this string", 7 | complex,another,different style, 8 | complex,ohno,"how ""it"" works now?", 9 | simple,hello,Hello,Ciao 10 | simple,bike,,Bicicletta 11 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-groups/include-exclude/exclude.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,hello,Hello,Ciao 3 | app,bike,Bike,Bicicletta 4 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-groups/include-exclude/include.stub: -------------------------------------------------------------------------------- 1 | group,key,en,fr 2 | app,hello,Hello,Salut 3 | app,bike,Bike,Vélo 4 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-groups/include-exclude/input-en.stub: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "Hello", 3 | "bike": "Bike" 4 | } 5 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-groups/include-exclude/input-fr.stub: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "Salut", 3 | "bike": "Vélo" 4 | } 5 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-groups/include-exclude/input-it.stub: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "Ciao", 3 | "bike": "Bicicletta" 4 | } 5 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-langs/base/input-en.stub: -------------------------------------------------------------------------------- 1 | { 2 | "complex.hello": "Hello", 3 | "complex.this.car": "This car", 4 | "complex.this.bus": "This bus", 5 | "complex.very.long.limousine": "It's a very long limousine", 6 | "complex.spaced key": "there is a column, separator in this string", 7 | "complex.another": "different style", 8 | "complex.ohno": "how \"it\" works now?", 9 | "simple.hello": "Hello" 10 | } 11 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-langs/base/input-it.stub: -------------------------------------------------------------------------------- 1 | { 2 | "complex.hello": "Ciao", 3 | "complex.this.car": "Questa macchina", 4 | "complex.this.bus": "Questo bus", 5 | "complex.very.long.limousine": "È una limousine molto lunga", 6 | "simple.hello": "Ciao", 7 | "simple.bike": "Bicicletta" 8 | } 9 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-langs/base/output.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | complex,hello,Hello,Ciao 3 | complex,this.car,This car,Questa macchina 4 | complex,this.bus,This bus,Questo bus 5 | complex,very.long.limousine,It's a very long limousine,È una limousine molto lunga 6 | complex,spaced key,"there is a column, separator in this string", 7 | complex,another,different style, 8 | complex,ohno,"how ""it"" works now?", 9 | simple,hello,Hello,Ciao 10 | simple,bike,,Bicicletta 11 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-langs/include-exclude/exclude.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,hello,Hello,Ciao 3 | app,bike,Bike,Bicicletta 4 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-langs/include-exclude/include.stub: -------------------------------------------------------------------------------- 1 | group,key,en,fr 2 | app,hello,Hello,Salut 3 | app,bike,Bike,Vélo 4 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-langs/include-exclude/input-en.stub: -------------------------------------------------------------------------------- 1 | { 2 | "app.hello": "Hello", 3 | "app.bike": "Bike" 4 | } 5 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-langs/include-exclude/input-fr.stub: -------------------------------------------------------------------------------- 1 | { 2 | "app.hello": "Salut", 3 | "app.bike": "Vélo" 4 | } 5 | -------------------------------------------------------------------------------- /tests/Stubs/importers/json-langs/include-exclude/input-it.stub: -------------------------------------------------------------------------------- 1 | { 2 | "app.hello": "Ciao", 3 | "app.bike": "Bicicletta" 4 | } 5 | -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/base/input-en-complex.stub: -------------------------------------------------------------------------------- 1 | 'Hello', 6 | 'this' => [ 7 | 'car' => 'This car', 8 | 'bus' => 'This bus' 9 | ], 10 | 'very' => [ 11 | 'long' => [ 12 | 'limousine' => 'It\'s a very long limousine' 13 | ] 14 | ], 15 | 'spaced key' => 'there is a column, separator in this string', 16 | "another" => "different style", 17 | 'ohno' => 'how "it" works now?', 18 | 19 | ]; -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/base/input-en-simple.stub: -------------------------------------------------------------------------------- 1 | 'Hello', 6 | 7 | ]; -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/base/input-it-complex.stub: -------------------------------------------------------------------------------- 1 | 'Ciao', 6 | 'this' => [ 7 | 'car' => 'Questa macchina', 8 | 'bus' => 'Questo bus' 9 | ], 10 | 'very' => [ 11 | 'long' => [ 12 | 'limousine' => 'È una limousine molto lunga' 13 | ] 14 | ] 15 | 16 | ]; -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/base/input-it-simple.stub: -------------------------------------------------------------------------------- 1 | 'Ciao', 6 | 'bike' => 'Bicicletta' 7 | 8 | ]; -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/base/output.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | complex,hello,Hello,Ciao 3 | complex,this.car,This car,Questa macchina 4 | complex,this.bus,This bus,Questo bus 5 | complex,very.long.limousine,It's a very long limousine,È una limousine molto lunga 6 | complex,spaced key,"there is a column, separator in this string", 7 | complex,another,different style, 8 | complex,ohno,"how ""it"" works now?", 9 | simple,hello,Hello,Ciao 10 | simple,bike,,Bicicletta 11 | -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/include-exclude/exclude.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,hello,Hello,Ciao 3 | app,bike,Bike,Bicicletta 4 | -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/include-exclude/include.stub: -------------------------------------------------------------------------------- 1 | group,key,en,fr 2 | app,hello,Hello,Salut 3 | app,bike,Bike,Vèlo 4 | -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/include-exclude/input-en.stub: -------------------------------------------------------------------------------- 1 | 'Hello', 6 | 'bike' => 'Bike' 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/include-exclude/input-fr.stub: -------------------------------------------------------------------------------- 1 | 'Salut', 6 | 'bike' => 'Vèlo' 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/include-exclude/input-it.stub: -------------------------------------------------------------------------------- 1 | 'Ciao', 6 | 'bike' => 'Bicicletta' 7 | 8 | ]; -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/source/input-ar.stub: -------------------------------------------------------------------------------- 1 | 'مرحبًا', 6 | 'bike' => 'دراجة هوائية', 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/source/input-en.stub: -------------------------------------------------------------------------------- 1 | 'Hello', 6 | 'bike' => 'Bike', 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/source/input-it.stub: -------------------------------------------------------------------------------- 1 | 'Ciao', 6 | 'bike' => 'Bicicletta', 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/source/output-ar.stub: -------------------------------------------------------------------------------- 1 | group,key,ar,en,it 2 | app,hello,مرحبًا,Hello,Ciao 3 | app,bike,دراجة هوائية,Bike,Bicicletta 4 | -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/source/output-en.stub: -------------------------------------------------------------------------------- 1 | group,key,en,ar,it 2 | app,hello,Hello,مرحبًا,Ciao 3 | app,bike,Bike,دراجة هوائية,Bicicletta 4 | -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/territory/input-en_GB-complex.stub: -------------------------------------------------------------------------------- 1 | 'Hello', 6 | 'this' => [ 7 | 'car' => 'This car', 8 | 'bus' => 'This bus' 9 | ], 10 | 'very' => [ 11 | 'long' => [ 12 | 'limousine' => 'It\'s a very long limousine' 13 | ] 14 | ], 15 | 'spaced key' => 'there is a column, separator in this string', 16 | "another" => "different style", 17 | 'ohno' => 'how "it" works now?', 18 | 19 | ]; -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/territory/input-en_GB-simple.stub: -------------------------------------------------------------------------------- 1 | 'Hello', 6 | 7 | ]; -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/territory/input-it-complex.stub: -------------------------------------------------------------------------------- 1 | 'Ciao', 6 | 'this' => [ 7 | 'car' => 'Questa macchina', 8 | 'bus' => 'Questo bus' 9 | ], 10 | 'very' => [ 11 | 'long' => [ 12 | 'limousine' => 'È una limousine molto lunga' 13 | ] 14 | ] 15 | 16 | ]; -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/territory/input-it-simple.stub: -------------------------------------------------------------------------------- 1 | 'Ciao', 6 | 'bike' => 'Bicicletta' 7 | 8 | ]; -------------------------------------------------------------------------------- /tests/Stubs/importers/laravel/territory/output.stub: -------------------------------------------------------------------------------- 1 | group,key,en-GB,it 2 | complex,hello,Hello,Ciao 3 | complex,this.car,This car,Questa macchina 4 | complex,this.bus,This bus,Questo bus 5 | complex,very.long.limousine,It's a very long limousine,È una limousine molto lunga 6 | complex,spaced key,"there is a column, separator in this string", 7 | complex,another,different style, 8 | complex,ohno,"how ""it"" works now?", 9 | simple,hello,Hello,Ciao 10 | simple,bike,,Bicicletta 11 | -------------------------------------------------------------------------------- /tests/Stubs/linters/concurrent-key/failure.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | add,a.b,filled,riempito 3 | add,a.b.c,filled,riempito 4 | add,a,filled,riempito 5 | app,b,filled,riempito 6 | app,c.a,filled,riempito 7 | app,c.b,filled,riempito 8 | add,d,filled,riempito 9 | add,d.a,filled,riempito 10 | app,e.a,filled,riempito 11 | app,e,filled,riempito 12 | app,f,filled,riempito 13 | app,f.a,filled,riempito 14 | app,f.b,filled,riempito 15 | app,g.a,filled,riempito 16 | app,g.b,filled,riempito 17 | app,g.a.b,filled,riempito 18 | -------------------------------------------------------------------------------- /tests/Stubs/linters/concurrent-key/success.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,apple,Apple,Mela 3 | app,ark,Ark,Arca -------------------------------------------------------------------------------- /tests/Stubs/linters/duplicate-key/failure.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,apple,Apple,Mela 3 | app,apple,Ark,Arca -------------------------------------------------------------------------------- /tests/Stubs/linters/duplicate-key/success.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,apple,Apple,Mela 3 | app,ark,Ark,Arca -------------------------------------------------------------------------------- /tests/Stubs/linters/duplicate-value/failure.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,apple,Apple,Mela 3 | app,ark,Ark,Ark -------------------------------------------------------------------------------- /tests/Stubs/linters/duplicate-value/success.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,apple,Apple,Mela 3 | app,ark,Ark,Arca -------------------------------------------------------------------------------- /tests/Stubs/linters/no-value/failure.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,a,,riempito 3 | app,b,filled, 4 | app,c,, 5 | app,d,filled,riempito -------------------------------------------------------------------------------- /tests/Stubs/linters/no-value/success.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,apple,Apple,Mela 3 | app,ark,Ark,Arca -------------------------------------------------------------------------------- /tests/Stubs/linters/same-parameters/failure.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it,es 2 | app,hello,My name is :name and I'm :age.,Il nome è :name.,Tengo :age años. 3 | app,cat,The :animal is on the table.,Il :animal è sul :thing.,El animal está sobre la mesa. 4 | app,yep,Yes :who can,Sì :who può,Sí :who puede 5 | app,nope,Nope,No,No 6 | app,bye,Bye :name,Ciao :name, 7 | -------------------------------------------------------------------------------- /tests/Stubs/linters/same-parameters/success.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,hello,My name is :name and I'm :age.,Il nome è :name e ho :age anni. 3 | -------------------------------------------------------------------------------- /tests/Stubs/linters/untranslated-strings/blade.stub: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Laravel 8 | 9 | 10 | 11 | 12 | 13 | 65 | 66 | 67 |
68 | @if (Route::has('login')) 69 | 80 | @endif 81 | 82 |
83 |
84 | Laravel 85 |
86 | 87 | 97 |
98 |
99 | 100 | 101 | -------------------------------------------------------------------------------- /tests/Stubs/linters/untranslated-strings/failure.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,docs,Documentation,Documentazione -------------------------------------------------------------------------------- /tests/Stubs/linters/untranslated-strings/success.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,docs,Documentation,Documentazione 3 | app,news,News,Notizie -------------------------------------------------------------------------------- /tests/Stubs/linters/unused-strings/blade.stub: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Laravel 8 | 9 | 10 | 11 | 12 | 13 | 65 | 66 | 67 |
68 | @if (Route::has('login')) 69 | 80 | @endif 81 | 82 |
83 |
84 | Laravel 85 |
86 | 87 | 97 |
98 |
99 | 100 | 101 | -------------------------------------------------------------------------------- /tests/Stubs/linters/unused-strings/failure.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,docs,Documentation,Documentazione 3 | app,news,News,Notizie 4 | app,apple,Apple,Mela -------------------------------------------------------------------------------- /tests/Stubs/linters/unused-strings/success.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,docs,Documentation,Documentazione 3 | app,news,News,Notizie -------------------------------------------------------------------------------- /tests/Stubs/linters/valid-header/invalid-first-column.stub: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /tests/Stubs/linters/valid-header/invalid-second-column.stub: -------------------------------------------------------------------------------- 1 | group,test -------------------------------------------------------------------------------- /tests/Stubs/linters/valid-header/missing-first-column.stub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lukasss93/laravel-larex/51106853e8e4c20f074896dd3b32dc7b96b8eee4/tests/Stubs/linters/valid-header/missing-first-column.stub -------------------------------------------------------------------------------- /tests/Stubs/linters/valid-header/missing-second-column.stub: -------------------------------------------------------------------------------- 1 | group -------------------------------------------------------------------------------- /tests/Stubs/linters/valid-header/no-language-columns.stub: -------------------------------------------------------------------------------- 1 | group,key -------------------------------------------------------------------------------- /tests/Stubs/linters/valid-header/success.stub: -------------------------------------------------------------------------------- 1 | group,key,en -------------------------------------------------------------------------------- /tests/Stubs/linters/valid-html-value/failure.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,apple,Apple,Mela 3 | app,ark,Ark,Ark -------------------------------------------------------------------------------- /tests/Stubs/linters/valid-html-value/success.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it 2 | app,apple,Apple,Mela 3 | app,ark,Ark,Arca -------------------------------------------------------------------------------- /tests/Stubs/linters/valid-language-code/failure-and-suggest.stub: -------------------------------------------------------------------------------- 1 | group,key,it-IF 2 | -------------------------------------------------------------------------------- /tests/Stubs/linters/valid-language-code/failure.stub: -------------------------------------------------------------------------------- 1 | group,key,xxx -------------------------------------------------------------------------------- /tests/Stubs/linters/valid-language-code/success.stub: -------------------------------------------------------------------------------- 1 | group,key,en,it -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 |