├── .actrc ├── .distignore ├── .editorconfig ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE ├── PULL_REQUEST_TEMPLATE ├── dependabot.yml └── workflows │ ├── code-quality.yml │ ├── regenerate-readme.yml │ └── testing.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── behat.yml ├── composer.json ├── features ├── language-core.feature ├── language-plugin.feature └── language-theme.feature ├── language-command.php ├── phpcs.xml.dist ├── phpstan.neon.dist ├── src ├── Core_Language_Command.php ├── Language_Namespace.php ├── Plugin_Language_Command.php ├── Site_Switch_Language_Command.php ├── Theme_Language_Command.php └── WP_CLI │ ├── CommandWithTranslation.php │ └── LanguagePackUpgrader.php └── wp-cli.yml /.actrc: -------------------------------------------------------------------------------- 1 | # Configuration file for nektos/act. 2 | # See https://github.com/nektos/act#configuration 3 | -P ubuntu-latest=shivammathur/node:latest 4 | -------------------------------------------------------------------------------- /.distignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .git 3 | .gitignore 4 | .gitlab-ci.yml 5 | .editorconfig 6 | .travis.yml 7 | behat.yml 8 | circle.yml 9 | phpcs.xml.dist 10 | phpunit.xml.dist 11 | bin/ 12 | features/ 13 | utils/ 14 | *.zip 15 | *.tar.gz 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | # From https://github.com/WordPress/wordpress-develop/blob/trunk/.editorconfig with a couple of additions. 8 | 9 | root = true 10 | 11 | [*] 12 | charset = utf-8 13 | end_of_line = lf 14 | insert_final_newline = true 15 | trim_trailing_whitespace = true 16 | indent_style = tab 17 | 18 | [{*.yml,*.feature,.jshintrc,*.json}] 19 | indent_style = space 20 | indent_size = 2 21 | 22 | [*.md] 23 | trim_trailing_whitespace = false 24 | 25 | [{*.txt,wp-config-sample.php}] 26 | end_of_line = crlf 27 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @wp-cli/committers 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: composer 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | labels: 9 | - scope:distribution 10 | - package-ecosystem: github-actions 11 | directory: "/" 12 | schedule: 13 | interval: daily 14 | open-pull-requests-limit: 10 15 | labels: 16 | - scope:distribution 17 | 18 | -------------------------------------------------------------------------------- /.github/workflows/code-quality.yml: -------------------------------------------------------------------------------- 1 | name: Code Quality Checks 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | 10 | jobs: 11 | code-quality: 12 | uses: wp-cli/.github/.github/workflows/reusable-code-quality.yml@main 13 | -------------------------------------------------------------------------------- /.github/workflows/regenerate-readme.yml: -------------------------------------------------------------------------------- 1 | name: Regenerate README file 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | paths-ignore: 10 | - "features/**" 11 | - "README.md" 12 | 13 | jobs: 14 | regenerate-readme: 15 | uses: wp-cli/.github/.github/workflows/reusable-regenerate-readme.yml@main 16 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: Testing 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | - master 10 | schedule: 11 | - cron: '17 1 * * *' # Run every day on a seemly random time. 12 | 13 | jobs: 14 | test: 15 | uses: wp-cli/.github/.github/workflows/reusable-testing.yml@main 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | wp-cli.local.yml 3 | node_modules/ 4 | vendor/ 5 | *.zip 6 | *.tar.gz 7 | composer.lock 8 | *.log 9 | phpunit.xml 10 | phpcs.xml 11 | .phpcs.xml 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | We appreciate you taking the initiative to contribute to this project. 5 | 6 | Contributing isn’t limited to just code. We encourage you to contribute in the way that best fits your abilities, by writing tutorials, giving a demo at your local meetup, helping other users with their support questions, or revising our documentation. 7 | 8 | For a more thorough introduction, [check out WP-CLI's guide to contributing](https://make.wordpress.org/cli/handbook/contributing/). This package follows those policy and guidelines. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) 2011-2018 WP-CLI Development Group (https://github.com/wp-cli/language-command/contributors) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wp-cli/language-command 2 | ======================= 3 | 4 | Installs, activates, and manages language packs. 5 | 6 | [![Testing](https://github.com/wp-cli/language-command/actions/workflows/testing.yml/badge.svg)](https://github.com/wp-cli/language-command/actions/workflows/testing.yml) 7 | 8 | Quick links: [Using](#using) | [Installing](#installing) | [Contributing](#contributing) | [Support](#support) 9 | 10 | ## Using 11 | 12 | This package implements the following commands: 13 | 14 | ### wp language 15 | 16 | Installs, activates, and manages language packs. 17 | 18 | ~~~ 19 | wp language 20 | ~~~ 21 | 22 | **EXAMPLES** 23 | 24 | # Install the Dutch core language pack. 25 | $ wp language core install nl_NL 26 | Downloading translation from https://downloads.wordpress.org/translation/core/6.4.3/nl_NL.zip... 27 | Unpacking the update... 28 | Installing the latest version... 29 | Removing the old version of the translation... 30 | Translation updated successfully. 31 | Language 'nl_NL' installed. 32 | Success: Installed 1 of 1 languages. 33 | 34 | # Activate the Dutch core language pack. 35 | $ wp site switch-language nl_NL 36 | Success: Language activated. 37 | 38 | # Install the Dutch theme language pack for Twenty Ten. 39 | $ wp language theme install twentyten nl_NL 40 | Downloading translation from https://downloads.wordpress.org/translation/theme/twentyten/4.0/nl_NL.zip... 41 | Unpacking the update... 42 | Installing the latest version... 43 | Removing the old version of the translation... 44 | Translation updated successfully. 45 | Language 'nl_NL' installed. 46 | Success: Installed 1 of 1 languages. 47 | 48 | # Install the Dutch plugin language pack for Hello Dolly. 49 | $ wp language plugin install hello-dolly nl_NL 50 | Downloading translation from https://downloads.wordpress.org/translation/plugin/hello-dolly/1.7.2/nl_NL.zip... 51 | Unpacking the update... 52 | Installing the latest version... 53 | Removing the old version of the translation... 54 | Translation updated successfully. 55 | Language 'nl_NL' installed. 56 | Success: Installed 1 of 1 languages. 57 | 58 | 59 | 60 | ### wp language core 61 | 62 | Installs, activates, and manages core language packs. 63 | 64 | ~~~ 65 | wp language core 66 | ~~~ 67 | 68 | **EXAMPLES** 69 | 70 | # Install the Dutch core language pack. 71 | $ wp language core install nl_NL 72 | Downloading translation from https://downloads.wordpress.org/translation/core/6.4.3/nl_NL.zip... 73 | Unpacking the update... 74 | Installing the latest version... 75 | Removing the old version of the translation... 76 | Translation updated successfully. 77 | Language 'nl_NL' installed. 78 | Success: Installed 1 of 1 languages. 79 | 80 | # Activate the Dutch core language pack. 81 | $ wp site switch-language nl_NL 82 | Success: Language activated. 83 | 84 | # Uninstall the Dutch core language pack. 85 | $ wp language core uninstall nl_NL 86 | Success: Language uninstalled. 87 | 88 | # List installed core language packs. 89 | $ wp language core list --status=installed 90 | +----------+--------------+-------------+-----------+-----------+---------------------+ 91 | | language | english_name | native_name | status | update | updated | 92 | +----------+--------------+-------------+-----------+-----------+---------------------+ 93 | | nl_NL | Dutch | Nederlands | installed | available | 2024-01-31 10:24:06 | 94 | +----------+--------------+-------------+-----------+-----------+---------------------+ 95 | 96 | 97 | 98 | ### wp language core activate 99 | 100 | Activates a given language. 101 | 102 | ~~~ 103 | wp language core activate 104 | ~~~ 105 | 106 | **Warning: `wp language core activate` is deprecated. Use `wp site switch-language` instead.** 107 | 108 | **OPTIONS** 109 | 110 | 111 | Language code to activate. 112 | 113 | **EXAMPLES** 114 | 115 | # Activate the given language. 116 | $ wp language core activate ja 117 | Success: Language activated. 118 | 119 | 120 | 121 | ### wp language core is-installed 122 | 123 | Checks if a given language is installed. 124 | 125 | ~~~ 126 | wp language core is-installed 127 | ~~~ 128 | 129 | Returns exit code 0 when installed, 1 when uninstalled. 130 | 131 | **OPTIONS** 132 | 133 | 134 | The language code to check. 135 | 136 | **EXAMPLES** 137 | 138 | # Check whether the German language is installed; exit status 0 if installed, otherwise 1. 139 | $ wp language core is-installed de_DE 140 | $ echo $? 141 | 1 142 | 143 | 144 | 145 | ### wp language core install 146 | 147 | Installs a given language. 148 | 149 | ~~~ 150 | wp language core install ... [--activate] 151 | ~~~ 152 | 153 | Downloads the language pack from WordPress.org. Find your language code at: https://translate.wordpress.org/ 154 | 155 | **OPTIONS** 156 | 157 | ... 158 | Language code to install. 159 | 160 | [--activate] 161 | If set, the language will be activated immediately after install. 162 | 163 | **EXAMPLES** 164 | 165 | # Install the Brazilian Portuguese language. 166 | $ wp language core install pt_BR 167 | Downloading translation from https://downloads.wordpress.org/translation/core/6.5/pt_BR.zip... 168 | Unpacking the update... 169 | Installing the latest version... 170 | Removing the old version of the translation... 171 | Translation updated successfully. 172 | Language 'pt_BR' installed. 173 | Success: Installed 1 of 1 languages. 174 | 175 | 176 | 177 | ### wp language core list 178 | 179 | Lists all available languages. 180 | 181 | ~~~ 182 | wp language core list [--field=] [--=] [--fields=] [--format=] 183 | ~~~ 184 | 185 | **OPTIONS** 186 | 187 | [--field=] 188 | Display the value of a single field 189 | 190 | [--=] 191 | Filter results by key=value pairs. 192 | 193 | [--fields=] 194 | Limit the output to specific fields. 195 | 196 | [--format=] 197 | Render output in a particular format. 198 | --- 199 | default: table 200 | options: 201 | - table 202 | - csv 203 | - json 204 | - count 205 | --- 206 | 207 | **AVAILABLE FIELDS** 208 | 209 | These fields will be displayed by default for each translation: 210 | 211 | * language 212 | * english_name 213 | * native_name 214 | * status 215 | * update 216 | * updated 217 | 218 | **EXAMPLES** 219 | 220 | # List language,english_name,status fields of available languages. 221 | $ wp language core list --fields=language,english_name,status 222 | +----------------+-------------------------+-------------+ 223 | | language | english_name | status | 224 | +----------------+-------------------------+-------------+ 225 | | ar | Arabic | uninstalled | 226 | | ary | Moroccan Arabic | uninstalled | 227 | | az | Azerbaijani | uninstalled | 228 | 229 | 230 | 231 | ### wp language core uninstall 232 | 233 | Uninstalls a given language. 234 | 235 | ~~~ 236 | wp language core uninstall ... 237 | ~~~ 238 | 239 | **OPTIONS** 240 | 241 | ... 242 | Language code to uninstall. 243 | 244 | **EXAMPLES** 245 | 246 | # Uninstall the Japanese core language pack. 247 | $ wp language core uninstall ja 248 | Success: Language uninstalled. 249 | 250 | 251 | 252 | ### wp language core update 253 | 254 | Updates installed languages for core. 255 | 256 | ~~~ 257 | wp language core update [--dry-run] 258 | ~~~ 259 | 260 | **OPTIONS** 261 | 262 | [--dry-run] 263 | Preview which translations would be updated. 264 | 265 | **EXAMPLES** 266 | 267 | # Update installed core languages packs. 268 | $ wp language core update 269 | Updating 'Japanese' translation for WordPress 6.4.3... 270 | Downloading translation from https://downloads.wordpress.org/translation/core/6.4.3/ja.zip... 271 | Translation updated successfully. 272 | Success: Updated 1/1 translation. 273 | 274 | 275 | 276 | ### wp language plugin 277 | 278 | Installs, activates, and manages plugin language packs. 279 | 280 | ~~~ 281 | wp language plugin 282 | ~~~ 283 | 284 | **EXAMPLES** 285 | 286 | # Install the Dutch plugin language pack for Hello Dolly. 287 | $ wp language plugin install hello-dolly nl_NL 288 | Downloading translation from https://downloads.wordpress.org/translation/plugin/hello-dolly/1.7.2/nl_NL.zip... 289 | Unpacking the update... 290 | Installing the latest version... 291 | Removing the old version of the translation... 292 | Translation updated successfully. 293 | Language 'nl_NL' installed. 294 | Success: Installed 1 of 1 languages. 295 | 296 | # Uninstall the Dutch plugin language pack for Hello Dolly. 297 | $ wp language plugin uninstall hello-dolly nl_NL 298 | Language 'nl_NL' for 'hello-dolly' uninstalled. 299 | +-------------+--------+-------------+ 300 | | name | locale | status | 301 | +-------------+--------+-------------+ 302 | | hello-dolly | nl_NL | uninstalled | 303 | +-------------+--------+-------------+ 304 | Success: Uninstalled 1 of 1 languages. 305 | 306 | # List installed plugin language packs for Hello Dolly. 307 | $ wp language plugin list hello-dolly --status=installed 308 | +-------------+----------+--------------+-------------+-----------+--------+---------------------+ 309 | | plugin | language | english_name | native_name | status | update | updated | 310 | +-------------+----------+--------------+-------------+-----------+--------+---------------------+ 311 | | hello-dolly | nl_NL | Dutch | Nederlands | installed | none | 2023-11-13 12:34:15 | 312 | +-------------+----------+--------------+-------------+-----------+--------+---------------------+ 313 | 314 | 315 | 316 | ### wp language plugin is-installed 317 | 318 | Checks if a given language is installed. 319 | 320 | ~~~ 321 | wp language plugin is-installed ... 322 | ~~~ 323 | 324 | Returns exit code 0 when installed, 1 when uninstalled. 325 | 326 | **OPTIONS** 327 | 328 | 329 | Plugin to check for. 330 | 331 | ... 332 | The language code to check. 333 | 334 | **EXAMPLES** 335 | 336 | # Check whether the German language is installed for Akismet; exit status 0 if installed, otherwise 1. 337 | $ wp language plugin is-installed akismet de_DE 338 | $ echo $? 339 | 1 340 | 341 | 342 | 343 | ### wp language plugin install 344 | 345 | Installs a given language for a plugin. 346 | 347 | ~~~ 348 | wp language plugin install [] [--all] ... [--format=] 349 | ~~~ 350 | 351 | Downloads the language pack from WordPress.org. 352 | 353 | **OPTIONS** 354 | 355 | [] 356 | Plugin to install language for. 357 | 358 | [--all] 359 | If set, languages for all plugins will be installed. 360 | 361 | ... 362 | Language code to install. 363 | 364 | [--format=] 365 | Render output in a particular format. Used when installing languages for all plugins. 366 | --- 367 | default: table 368 | options: 369 | - table 370 | - csv 371 | - json 372 | - summary 373 | --- 374 | 375 | **EXAMPLES** 376 | 377 | # Install the Japanese language for Akismet. 378 | $ wp language plugin install akismet ja 379 | Downloading translation from https://downloads.wordpress.org/translation/plugin/akismet/4.0.3/ja.zip... 380 | Unpacking the update... 381 | Installing the latest version... 382 | Removing the old version of the translation... 383 | Translation updated successfully. 384 | Language 'ja' installed. 385 | Success: Installed 1 of 1 languages. 386 | 387 | 388 | 389 | ### wp language plugin list 390 | 391 | Lists all available languages for one or more plugins. 392 | 393 | ~~~ 394 | wp language plugin list [...] [--all] [--field=] [--=] [--fields=] [--format=] 395 | ~~~ 396 | 397 | **OPTIONS** 398 | 399 | [...] 400 | One or more plugins to list languages for. 401 | 402 | [--all] 403 | If set, available languages for all plugins will be listed. 404 | 405 | [--field=] 406 | Display the value of a single field. 407 | 408 | [--=] 409 | Filter results by key=value pairs. 410 | 411 | [--fields=] 412 | Limit the output to specific fields. 413 | 414 | [--format=] 415 | Render output in a particular format. 416 | --- 417 | default: table 418 | options: 419 | - table 420 | - csv 421 | - json 422 | - count 423 | --- 424 | 425 | **AVAILABLE FIELDS** 426 | 427 | These fields will be displayed by default for each translation: 428 | 429 | * plugin 430 | * language 431 | * english_name 432 | * native_name 433 | * status 434 | * update 435 | * updated 436 | 437 | **EXAMPLES** 438 | 439 | # List available language packs for the plugin. 440 | $ wp language plugin list hello-dolly --fields=language,english_name,status 441 | +----------------+-------------------------+-------------+ 442 | | language | english_name | status | 443 | +----------------+-------------------------+-------------+ 444 | | ar | Arabic | uninstalled | 445 | | ary | Moroccan Arabic | uninstalled | 446 | | az | Azerbaijani | uninstalled | 447 | 448 | 449 | 450 | ### wp language plugin uninstall 451 | 452 | Uninstalls a given language for a plugin. 453 | 454 | ~~~ 455 | wp language plugin uninstall [] [--all] ... [--format=] 456 | ~~~ 457 | 458 | **OPTIONS** 459 | 460 | [] 461 | Plugin to uninstall language for. 462 | 463 | [--all] 464 | If set, languages for all plugins will be uninstalled. 465 | 466 | ... 467 | Language code to uninstall. 468 | 469 | [--format=] 470 | Render output in a particular format. Used when installing languages for all plugins. 471 | --- 472 | default: table 473 | options: 474 | - table 475 | - csv 476 | - json 477 | - summary 478 | --- 479 | 480 | **EXAMPLES** 481 | 482 | # Uninstall the Japanese plugin language pack for Hello Dolly. 483 | $ wp language plugin uninstall hello-dolly ja 484 | Language 'ja' for 'hello-dolly' uninstalled. 485 | +-------------+--------+-------------+ 486 | | name | locale | status | 487 | +-------------+--------+-------------+ 488 | | hello-dolly | ja | uninstalled | 489 | +-------------+--------+-------------+ 490 | Success: Uninstalled 1 of 1 languages. 491 | 492 | 493 | 494 | ### wp language plugin update 495 | 496 | Updates installed languages for one or more plugins. 497 | 498 | ~~~ 499 | wp language plugin update [...] [--all] [--dry-run] 500 | ~~~ 501 | 502 | **OPTIONS** 503 | 504 | [...] 505 | One or more plugins to update languages for. 506 | 507 | [--all] 508 | If set, languages for all plugins will be updated. 509 | 510 | [--dry-run] 511 | Preview which translations would be updated. 512 | 513 | **EXAMPLES** 514 | 515 | # Update all installed language packs for all plugins. 516 | $ wp language plugin update --all 517 | Updating 'Japanese' translation for Akismet 3.1.11... 518 | Downloading translation from https://downloads.wordpress.org/translation/plugin/akismet/3.1.11/ja.zip... 519 | Translation updated successfully. 520 | Success: Updated 1/1 translation. 521 | 522 | 523 | 524 | ### wp language theme 525 | 526 | Installs, activates, and manages theme language packs. 527 | 528 | ~~~ 529 | wp language theme 530 | ~~~ 531 | 532 | **EXAMPLES** 533 | 534 | # Install the Dutch theme language pack for Twenty Ten. 535 | $ wp language theme install twentyten nl_NL 536 | Downloading translation from https://downloads.wordpress.org/translation/theme/twentyten/4.0/nl_NL.zip... 537 | Unpacking the update... 538 | Installing the latest version... 539 | Removing the old version of the translation... 540 | Translation updated successfully. 541 | Language 'nl_NL' installed. 542 | Success: Installed 1 of 1 languages. 543 | 544 | # Uninstall the Dutch theme language pack for Twenty Ten. 545 | $ wp language theme uninstall twentyten nl_NL 546 | Language 'nl_NL' for 'twentyten' uninstalled. 547 | +-----------+--------+-------------+ 548 | | name | locale | status | 549 | +-----------+--------+-------------+ 550 | | twentyten | nl_NL | uninstalled | 551 | +-----------+--------+-------------+ 552 | Success: Uninstalled 1 of 1 languages. 553 | 554 | # List installed theme language packs for Twenty Ten. 555 | $ wp language theme list twentyten --status=installed 556 | +-----------+----------+--------------+-------------+-----------+--------+---------------------+ 557 | | theme | language | english_name | native_name | status | update | updated | 558 | +-----------+----------+--------------+-------------+-----------+--------+---------------------+ 559 | | twentyten | nl_NL | Dutch | Nederlands | installed | none | 2023-12-29 21:21:39 | 560 | +-----------+----------+--------------+-------------+-----------+--------+---------------------+ 561 | 562 | 563 | 564 | ### wp language theme is-installed 565 | 566 | Checks if a given language is installed. 567 | 568 | ~~~ 569 | wp language theme is-installed ... 570 | ~~~ 571 | 572 | Returns exit code 0 when installed, 1 when uninstalled. 573 | 574 | **OPTIONS** 575 | 576 | 577 | Theme to check for. 578 | 579 | ... 580 | The language code to check. 581 | 582 | **EXAMPLES** 583 | 584 | # Check whether the German language is installed for Twenty Seventeen; exit status 0 if installed, otherwise 1. 585 | $ wp language theme is-installed twentyseventeen de_DE 586 | $ echo $? 587 | 1 588 | 589 | 590 | 591 | ### wp language theme install 592 | 593 | Installs a given language for a theme. 594 | 595 | ~~~ 596 | wp language theme install [] [--all] ... [--format=] 597 | ~~~ 598 | 599 | Downloads the language pack from WordPress.org. 600 | 601 | **OPTIONS** 602 | 603 | [] 604 | Theme to install language for. 605 | 606 | [--all] 607 | If set, languages for all themes will be installed. 608 | 609 | ... 610 | Language code to install. 611 | 612 | [--format=] 613 | Render output in a particular format. Used when installing languages for all themes. 614 | --- 615 | default: table 616 | options: 617 | - table 618 | - csv 619 | - json 620 | - summary 621 | --- 622 | 623 | **EXAMPLES** 624 | 625 | # Install the Japanese language for Twenty Seventeen. 626 | $ wp language theme install twentyseventeen ja 627 | Downloading translation from https://downloads.wordpress.org/translation/theme/twentyseventeen/1.3/ja.zip... 628 | Unpacking the update... 629 | Installing the latest version... 630 | Translation updated successfully. 631 | Language 'ja' installed. 632 | Success: Installed 1 of 1 languages. 633 | 634 | 635 | 636 | ### wp language theme list 637 | 638 | Lists all available languages for one or more themes. 639 | 640 | ~~~ 641 | wp language theme list [...] [--all] [--field=] [--=] [--fields=] [--format=] 642 | ~~~ 643 | 644 | **OPTIONS** 645 | 646 | [...] 647 | One or more themes to list languages for. 648 | 649 | [--all] 650 | If set, available languages for all themes will be listed. 651 | 652 | [--field=] 653 | Display the value of a single field. 654 | 655 | [--=] 656 | Filter results by key=value pairs. 657 | 658 | [--fields=] 659 | Limit the output to specific fields. 660 | 661 | [--format=] 662 | Render output in a particular format. 663 | --- 664 | default: table 665 | options: 666 | - table 667 | - csv 668 | - json 669 | - count 670 | --- 671 | 672 | **AVAILABLE FIELDS** 673 | 674 | These fields will be displayed by default for each translation: 675 | 676 | * theme 677 | * language 678 | * english_name 679 | * native_name 680 | * status 681 | * update 682 | * updated 683 | 684 | **EXAMPLES** 685 | 686 | # List available language packs for the theme. 687 | $ wp language theme list twentyten --fields=language,english_name,status 688 | +----------------+-------------------------+-------------+ 689 | | language | english_name | status | 690 | +----------------+-------------------------+-------------+ 691 | | ar | Arabic | uninstalled | 692 | | ary | Moroccan Arabic | uninstalled | 693 | | az | Azerbaijani | uninstalled | 694 | 695 | 696 | 697 | ### wp language theme uninstall 698 | 699 | Uninstalls a given language for a theme. 700 | 701 | ~~~ 702 | wp language theme uninstall [] [--all] ... [--format=] 703 | ~~~ 704 | 705 | **OPTIONS** 706 | 707 | [] 708 | Theme to uninstall language for. 709 | 710 | [--all] 711 | If set, languages for all themes will be uninstalled. 712 | 713 | ... 714 | Language code to uninstall. 715 | 716 | [--format=] 717 | Render output in a particular format. Used when installing languages for all themes. 718 | --- 719 | default: table 720 | options: 721 | - table 722 | - csv 723 | - json 724 | - summary 725 | --- 726 | 727 | **EXAMPLES** 728 | 729 | # Uninstall the Japanese theme language pack for Twenty Ten. 730 | $ wp language theme uninstall twentyten ja 731 | Language 'ja' for 'twentyten' uninstalled. 732 | +-----------+--------+-------------+ 733 | | name | locale | status | 734 | +-----------+--------+-------------+ 735 | | twentyten | ja | uninstalled | 736 | +-----------+--------+-------------+ 737 | Success: Uninstalled 1 of 1 languages. 738 | 739 | 740 | 741 | ### wp language theme update 742 | 743 | Updates installed languages for one or more themes. 744 | 745 | ~~~ 746 | wp language theme update [...] [--all] [--dry-run] 747 | ~~~ 748 | 749 | **OPTIONS** 750 | 751 | [...] 752 | One or more themes to update languages for. 753 | 754 | [--all] 755 | If set, languages for all themes will be updated. 756 | 757 | [--dry-run] 758 | Preview which translations would be updated. 759 | 760 | **EXAMPLES** 761 | 762 | # Update all installed language packs for all themes. 763 | $ wp language theme update --all 764 | Updating 'Japanese' translation for Twenty Fifteen 1.5... 765 | Downloading translation from https://downloads.wordpress.org/translation/theme/twentyfifteen/1.5/ja.zip... 766 | Translation updated successfully. 767 | Success: Updated 1/1 translation. 768 | 769 | 770 | 771 | ### wp site switch-language 772 | 773 | Activates a given language. 774 | 775 | ~~~ 776 | wp site switch-language 777 | ~~~ 778 | 779 | **OPTIONS** 780 | 781 | 782 | Language code to activate. 783 | 784 | **EXAMPLES** 785 | 786 | $ wp site switch-language ja 787 | Success: Language activated. 788 | 789 | ## Installing 790 | 791 | This package is included with WP-CLI itself, no additional installation necessary. 792 | 793 | To install the latest version of this package over what's included in WP-CLI, run: 794 | 795 | wp package install git@github.com:wp-cli/language-command.git 796 | 797 | ## Contributing 798 | 799 | We appreciate you taking the initiative to contribute to this project. 800 | 801 | Contributing isn’t limited to just code. We encourage you to contribute in the way that best fits your abilities, by writing tutorials, giving a demo at your local meetup, helping other users with their support questions, or revising our documentation. 802 | 803 | For a more thorough introduction, [check out WP-CLI's guide to contributing](https://make.wordpress.org/cli/handbook/contributing/). This package follows those policy and guidelines. 804 | 805 | ### Reporting a bug 806 | 807 | Think you’ve found a bug? We’d love for you to help us get it fixed. 808 | 809 | Before you create a new issue, you should [search existing issues](https://github.com/wp-cli/language-command/issues?q=label%3Abug%20) to see if there’s an existing resolution to it, or if it’s already been fixed in a newer version. 810 | 811 | Once you’ve done a bit of searching and discovered there isn’t an open or fixed issue for your bug, please [create a new issue](https://github.com/wp-cli/language-command/issues/new). Include as much detail as you can, and clear steps to reproduce if possible. For more guidance, [review our bug report documentation](https://make.wordpress.org/cli/handbook/bug-reports/). 812 | 813 | ### Creating a pull request 814 | 815 | Want to contribute a new feature? Please first [open a new issue](https://github.com/wp-cli/language-command/issues/new) to discuss whether the feature is a good fit for the project. 816 | 817 | Once you've decided to commit the time to seeing your pull request through, [please follow our guidelines for creating a pull request](https://make.wordpress.org/cli/handbook/pull-requests/) to make sure it's a pleasant experience. See "[Setting up](https://make.wordpress.org/cli/handbook/pull-requests/#setting-up)" for details specific to working on this package locally. 818 | 819 | ## Support 820 | 821 | GitHub issues aren't for general support questions, but there are other venues you can try: https://wp-cli.org/#support 822 | 823 | 824 | *This README.md is generated dynamically from the project's codebase using `wp scaffold package-readme` ([doc](https://github.com/wp-cli/scaffold-package-command#wp-scaffold-package-readme)). To suggest changes, please submit a pull request against the corresponding part of the codebase.* 825 | -------------------------------------------------------------------------------- /behat.yml: -------------------------------------------------------------------------------- 1 | default: 2 | suites: 3 | default: 4 | contexts: 5 | - WP_CLI\Tests\Context\FeatureContext 6 | paths: 7 | - features 8 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-cli/language-command", 3 | "type": "wp-cli-package", 4 | "description": "Installs, activates, and manages language packs.", 5 | "homepage": "https://github.com/wp-cli/language-command", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Daniel Bachhuber", 10 | "email": "daniel@runcommand.io", 11 | "homepage": "https://runcommand.io" 12 | } 13 | ], 14 | "require": { 15 | "wp-cli/wp-cli": "^2.12" 16 | }, 17 | "require-dev": { 18 | "wp-cli/db-command": "^1.3 || ^2", 19 | "wp-cli/entity-command": "^1.3 || ^2", 20 | "wp-cli/extension-command": "^1.2 || ^2", 21 | "wp-cli/wp-cli-tests": "^5" 22 | }, 23 | "config": { 24 | "process-timeout": 7200, 25 | "sort-packages": true, 26 | "allow-plugins": { 27 | "dealerdirect/phpcodesniffer-composer-installer": true, 28 | "johnpbloch/wordpress-core-installer": true, 29 | "phpstan/extension-installer": true 30 | }, 31 | "lock": false 32 | }, 33 | "extra": { 34 | "branch-alias": { 35 | "dev-main": "2.x-dev" 36 | }, 37 | "bundled": true, 38 | "commands": [ 39 | "language", 40 | "language core", 41 | "language core activate", 42 | "language core is-installed", 43 | "language core install", 44 | "language core list", 45 | "language core uninstall", 46 | "language core update", 47 | "language plugin", 48 | "language plugin is-installed", 49 | "language plugin install", 50 | "language plugin list", 51 | "language plugin uninstall", 52 | "language plugin update", 53 | "language theme", 54 | "language theme is-installed", 55 | "language theme install", 56 | "language theme list", 57 | "language theme uninstall", 58 | "language theme update", 59 | "site switch-language" 60 | ] 61 | }, 62 | "autoload": { 63 | "classmap": [ 64 | "src/" 65 | ], 66 | "files": [ 67 | "language-command.php" 68 | ] 69 | }, 70 | "minimum-stability": "dev", 71 | "prefer-stable": true, 72 | "scripts": { 73 | "behat": "run-behat-tests", 74 | "behat-rerun": "rerun-behat-tests", 75 | "lint": "run-linter-tests", 76 | "phpcs": "run-phpcs-tests", 77 | "phpstan": "run-phpstan-tests", 78 | "phpcbf": "run-phpcbf-cleanup", 79 | "phpunit": "run-php-unit-tests", 80 | "prepare-tests": "install-package-tests", 81 | "test": [ 82 | "@lint", 83 | "@phpcs", 84 | "@phpstan", 85 | "@phpunit", 86 | "@behat" 87 | ] 88 | }, 89 | "support": { 90 | "issues": "https://github.com/wp-cli/language-command/issues" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /features/language-core.feature: -------------------------------------------------------------------------------- 1 | Feature: Manage core translation files for a WordPress install 2 | 3 | @require-wp-4.0 4 | Scenario: Core translation CRUD 5 | Given a WP install 6 | And an empty cache 7 | 8 | When I run `wp language core list --fields=language,english_name,status` 9 | Then STDOUT should be a table containing rows: 10 | | language | english_name | status | 11 | | ar | Arabic | uninstalled | 12 | | en_AU | English (Australia) | uninstalled | 13 | | en_CA | English (Canada) | uninstalled | 14 | | en_GB | English (UK) | uninstalled | 15 | | en_US | English (United States) | active | 16 | | ja | Japanese | uninstalled | 17 | 18 | When I try `wp language core is-installed en_GB` 19 | Then the return code should be 1 20 | And STDERR should be empty 21 | 22 | When I try `wp language core is-installed en_AU` 23 | Then the return code should be 1 24 | And STDERR should be empty 25 | 26 | When I run `wp language core install en_GB` 27 | And I run `wp language core install en_AU` 28 | Then the wp-content/languages/admin-en_GB.po file should exist 29 | And the wp-content/languages/en_GB.po file should exist 30 | And the wp-content/languages/admin-en_AU.po file should exist 31 | And the wp-content/languages/en_AU.po file should exist 32 | And STDOUT should contain: 33 | """ 34 | Success: Installed 1 of 1 languages. 35 | """ 36 | And STDERR should be empty 37 | 38 | When I try `wp language core is-installed en_GB` 39 | Then the return code should be 0 40 | 41 | When I try `wp language core is-installed en_AU` 42 | Then the return code should be 0 43 | 44 | When I run `wp language core install en_CA ja` 45 | Then the wp-content/languages/admin-en_CA.po file should exist 46 | And the wp-content/languages/en_CA.po file should exist 47 | And the wp-content/languages/admin-ja.po file should exist 48 | And the wp-content/languages/ja.po file should exist 49 | And STDOUT should contain: 50 | """ 51 | Success: Installed 2 of 2 languages. 52 | """ 53 | And STDERR should be empty 54 | 55 | When I run `ls {SUITE_CACHE_DIR}/translation | grep core-default-` 56 | Then STDOUT should contain: 57 | """ 58 | en_AU 59 | """ 60 | And STDOUT should contain: 61 | """ 62 | en_GB 63 | """ 64 | 65 | When I try `wp language core install en_AU` 66 | Then STDOUT should be: 67 | """ 68 | Language 'en_AU' already installed. 69 | Success: Installed 0 of 1 languages (1 skipped). 70 | """ 71 | And STDERR should be empty 72 | And the return code should be 0 73 | 74 | When I run `wp language core list --fields=language,english_name,status` 75 | Then STDOUT should be a table containing rows: 76 | | language | english_name | status | 77 | | ar | Arabic | uninstalled | 78 | | en_GB | English (UK) | installed | 79 | 80 | When I run `wp language core list --status=installed --format=count` 81 | Then STDOUT should be: 82 | """ 83 | 4 84 | """ 85 | 86 | When I run `wp site switch-language en_GB` 87 | Then STDOUT should be: 88 | """ 89 | Success: Language activated. 90 | """ 91 | 92 | When I run `wp language core list --fields=language,english_name,update` 93 | Then STDOUT should be a table containing rows: 94 | | language | english_name | update | 95 | | ar | Arabic | none | 96 | | en_AU | English (Australia) | none | 97 | | en_CA | English (Canada) | none | 98 | | en_US | English (United States) | none | 99 | | en_GB | English (UK) | none | 100 | | ja | Japanese | none | 101 | 102 | When I run `wp language core update` 103 | Then STDOUT should contain: 104 | """ 105 | Success: Translations are up to date. 106 | """ 107 | 108 | When I run `wp language core list --field=language --status=active` 109 | Then STDOUT should be: 110 | """ 111 | en_GB 112 | """ 113 | 114 | When I run `wp language core list --fields=language,english_name,status` 115 | Then STDOUT should be a table containing rows: 116 | | language | english_name | status | 117 | | ar | Arabic | uninstalled | 118 | | en_GB | English (UK) | active | 119 | 120 | When I try `wp language core install en_AU --activate` 121 | Then STDERR should be empty 122 | And STDOUT should be: 123 | """ 124 | Language 'en_AU' already installed. 125 | Success: Language activated. 126 | Success: Installed 0 of 1 languages (1 skipped). 127 | """ 128 | And the return code should be 0 129 | 130 | When I try `wp language core install en_AU --activate` 131 | Then STDERR should contain: 132 | """ 133 | Warning: Language 'en_AU' already active. 134 | """ 135 | And STDOUT should contain: 136 | """ 137 | Language 'en_AU' already installed. 138 | Success: Installed 0 of 1 languages (1 skipped). 139 | """ 140 | And the return code should be 0 141 | 142 | When I try `wp language core install en_CA ja --activate` 143 | Then STDERR should be: 144 | """ 145 | Error: Only a single language can be active. 146 | """ 147 | And STDOUT should be empty 148 | And the return code should be 1 149 | 150 | When I run `wp site switch-language en_US` 151 | Then STDOUT should be: 152 | """ 153 | Success: Language activated. 154 | """ 155 | 156 | When I run `wp language core list --fields=language,english_name,status` 157 | Then STDOUT should be a table containing rows: 158 | | language | english_name | status | 159 | | ar | Arabic | uninstalled | 160 | | en_US | English (United States) | active | 161 | | en_GB | English (UK) | installed | 162 | 163 | When I try `wp site switch-language invalid_lang` 164 | Then STDERR should be: 165 | """ 166 | Error: Language not installed. 167 | """ 168 | And STDOUT should be empty 169 | And the return code should be 1 170 | 171 | When I run `wp language core uninstall en_GB` 172 | Then the wp-content/languages/admin-en_GB.po file should not exist 173 | And the wp-content/languages/en_GB.po file should not exist 174 | And the wp-content/languages/en_GB.l10n.php file should not exist 175 | And STDOUT should be: 176 | """ 177 | Success: Language uninstalled. 178 | """ 179 | 180 | When I run `wp language core uninstall en_CA ja` 181 | Then the wp-content/languages/admin-en_CA.po file should not exist 182 | And the wp-content/languages/en_CA.po file should not exist 183 | And the wp-content/languages/en_CA.l10n.php file should not exist 184 | And the wp-content/languages/admin-ja.po file should not exist 185 | And the wp-content/languages/ja.po file should not exist 186 | And the wp-content/languages/ja.l10n.php file should not exist 187 | And STDOUT should be: 188 | """ 189 | Success: Language uninstalled. 190 | Success: Language uninstalled. 191 | """ 192 | 193 | When I try `wp language core uninstall en_GB` 194 | Then STDERR should be: 195 | """ 196 | Error: Language not installed. 197 | """ 198 | And STDOUT should be empty 199 | And the return code should be 1 200 | 201 | When I run `wp language core install en_GB --activate` 202 | Then the wp-content/languages/admin-en_GB.po file should exist 203 | And the wp-content/languages/en_GB.po file should exist 204 | And STDOUT should contain: 205 | """ 206 | Success: Language activated. 207 | Success: Installed 1 of 1 languages. 208 | """ 209 | And STDERR should be empty 210 | 211 | When I try `wp language core install invalid_lang` 212 | Then STDERR should be: 213 | """ 214 | Warning: Language 'invalid_lang' not available. 215 | """ 216 | And STDOUT should be: 217 | """ 218 | Language 'invalid_lang' not installed. 219 | Success: Installed 0 of 1 languages (1 skipped). 220 | """ 221 | And the return code should be 0 222 | 223 | @require-wp-6.0 @require-php-7.2 224 | Scenario Outline: Core translation update in newer versions 225 | Given an empty directory 226 | And WP files 227 | And a database 228 | And I run `wp core download --version= --force` 229 | And wp-config.php 230 | And I run `wp core install --url='localhost:8001' --title='Test' --admin_user=wpcli --admin_email=admin@example.com --admin_password=1` 231 | 232 | When I run `wp language core list --fields=language,status,update` 233 | Then STDOUT should be a table containing rows: 234 | | language | status | update | 235 | | ar | uninstalled | none | 236 | | en_CA | uninstalled | none | 237 | | en_US | active | none | 238 | | ja | uninstalled | none | 239 | 240 | When I run `wp language core install en_CA ja` 241 | Then the wp-content/languages/admin-en_CA.po file should exist 242 | And the wp-content/languages/en_CA.po file should exist 243 | And the wp-content/languages/admin-ja.po file should exist 244 | And the wp-content/languages/ja.po file should exist 245 | And STDOUT should contain: 246 | """ 247 | Success: Installed 2 of 2 languages. 248 | """ 249 | And STDERR should be empty 250 | 251 | Given I try `wp core download --version= --force` 252 | Then the return code should be 0 253 | And I run `wp core update-db` 254 | 255 | When I run `wp language core list --fields=language,status,update` 256 | Then STDOUT should be a table containing rows: 257 | | language | status | update | 258 | | ar | uninstalled | none | 259 | | en_CA | installed | available | 260 | | en_US | active | none | 261 | | ja | installed | available | 262 | 263 | When I run `wp language core update --dry-run` 264 | Then STDOUT should contain: 265 | """ 266 | Found 2 translation updates that would be processed 267 | """ 268 | And STDOUT should contain: 269 | """ 270 | Core 271 | """ 272 | And STDOUT should contain: 273 | """ 274 | WordPress 275 | """ 276 | And STDOUT should contain: 277 | """ 278 | 279 | """ 280 | And STDOUT should contain: 281 | """ 282 | English (Canada) 283 | """ 284 | And STDOUT should contain: 285 | """ 286 | Japanese 287 | """ 288 | 289 | Examples: 290 | | original | update | 291 | | 6.5 | 6.6 | 292 | | 6.6.1 | 6.7 | 293 | 294 | @require-wp-4.0 295 | Scenario: Don't allow active language to be uninstalled 296 | Given a WP install 297 | 298 | When I run `wp language core install en_GB --activate` 299 | Then STDOUT should not be empty 300 | 301 | When I try `wp language core uninstall en_GB` 302 | Then STDERR should be: 303 | """ 304 | Warning: The 'en_GB' language is active. 305 | """ 306 | And STDOUT should be empty 307 | And the return code should be 0 308 | 309 | # This test downgrades to WordPress 5.6.14, but the SQLite plugin requires 6.0+ 310 | @require-wp-5.7 @require-mysql 311 | Scenario: Ensure correct language is installed for WP version 312 | Given a WP install 313 | And I try `wp theme install twentytwentyone` 314 | And I run `wp theme activate twentytwentyone` 315 | And an empty cache 316 | And I run `wp core download --version=5.6.14 --force` 317 | 318 | # PHP 8.2+ will show a warning for old WordPress core version. 319 | When I try `wp language core install nl_NL` 320 | Then STDOUT should contain: 321 | """ 322 | Downloading translation from https://downloads.wordpress.org/translation/core/5.6.14 323 | """ 324 | And STDOUT should contain: 325 | """ 326 | Success: Installed 1 of 1 languages. 327 | """ 328 | And the return code should be 0 329 | 330 | # This test downgrades to WordPress 5.4.1, but the SQLite plugin requires 6.0+ 331 | @require-wp-4.0 @require-mysql 332 | Scenario: Ensure upgrader output is in English 333 | Given a WP install 334 | And I try `wp theme install twentytwentyone` 335 | And I run `wp theme activate twentytwentyone` 336 | And an empty cache 337 | And I run `wp core download --version=5.4.1 --force` 338 | And a disable_sidebar_check.php file: 339 | """ 340 | $wpcli_language_check_requirements ) 23 | ); 24 | 25 | WP_CLI::add_command( 26 | 'language plugin', 27 | 'Plugin_Language_Command', 28 | array( 'before_invoke' => $wpcli_language_check_requirements ) 29 | ); 30 | 31 | WP_CLI::add_command( 32 | 'language theme', 33 | 'Theme_Language_Command', 34 | array( 'before_invoke' => $wpcli_language_check_requirements ) 35 | ); 36 | 37 | WP_CLI::add_hook( 38 | 'after_add_command:site', 39 | function () { 40 | WP_CLI::add_command( 'site switch-language', 'Site_Switch_Language_Command' ); 41 | } 42 | ); 43 | 44 | if ( class_exists( 'WP_CLI\Dispatcher\CommandNamespace' ) ) { 45 | WP_CLI::add_command( 'language', 'Language_Namespace' ); 46 | } 47 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | Custom ruleset for WP-CLI language-command 4 | 5 | 12 | 13 | 14 | . 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 38 | 39 | 41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 59 | 60 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | */src/WP_CLI/(CommandWithTranslation|LanguagePackUpgrader)\.php$ 78 | 79 | 80 | 81 | 82 | */src/(Core|Plugin|Site_Switch|Theme)_Language_Command\.php$ 83 | */src/Language_Namespace\.php$ 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 9 3 | paths: 4 | - src 5 | - language-command.php 6 | scanDirectories: 7 | - vendor/wp-cli/wp-cli/php 8 | scanFiles: 9 | - vendor/php-stubs/wordpress-stubs/wordpress-stubs.php 10 | treatPhpDocTypesAsCertain: false 11 | strictRules: 12 | uselessCast: true 13 | closureUsesThis: true 14 | overwriteVariablesWithLoop: true 15 | matchingInheritedMethodNames: true 16 | numericOperandsInArithmeticOperators: true 17 | switchConditionsMatchingType: true 18 | ignoreErrors: 19 | - identifier: missingType.iterableValue 20 | - identifier: missingType.property 21 | - identifier: missingType.parameter 22 | - identifier: missingType.return 23 | -------------------------------------------------------------------------------- /src/Core_Language_Command.php: -------------------------------------------------------------------------------- 1 | ] 54 | * : Display the value of a single field 55 | * 56 | * [--=] 57 | * : Filter results by key=value pairs. 58 | * 59 | * [--fields=] 60 | * : Limit the output to specific fields. 61 | * 62 | * [--format=] 63 | * : Render output in a particular format. 64 | * --- 65 | * default: table 66 | * options: 67 | * - table 68 | * - csv 69 | * - json 70 | * - count 71 | * --- 72 | * 73 | * ## AVAILABLE FIELDS 74 | * 75 | * These fields will be displayed by default for each translation: 76 | * 77 | * * language 78 | * * english_name 79 | * * native_name 80 | * * status 81 | * * update 82 | * * updated 83 | * 84 | * ## EXAMPLES 85 | * 86 | * # List language,english_name,status fields of available languages. 87 | * $ wp language core list --fields=language,english_name,status 88 | * +----------------+-------------------------+-------------+ 89 | * | language | english_name | status | 90 | * +----------------+-------------------------+-------------+ 91 | * | ar | Arabic | uninstalled | 92 | * | ary | Moroccan Arabic | uninstalled | 93 | * | az | Azerbaijani | uninstalled | 94 | * 95 | * @subcommand list 96 | * 97 | * @param string[] $args Positional arguments. Unused. 98 | * @param array{field?: string, format: string, language?: string, english_name?: string, native_name?: string, status?: string, update?: string, updated?: string} $assoc_args Associative arguments. 99 | */ 100 | public function list_( $args, $assoc_args ) { 101 | $translations = $this->get_all_languages(); 102 | $available = $this->get_installed_languages(); 103 | $updates = $this->get_translation_updates(); 104 | 105 | $current_locale = get_locale(); 106 | 107 | $translations = array_map( 108 | function ( $translation ) use ( $available, $current_locale, $updates ) { 109 | $translation['status'] = 'uninstalled'; 110 | if ( in_array( $translation['language'], $available, true ) ) { 111 | $translation['status'] = 'installed'; 112 | } 113 | 114 | if ( $current_locale === $translation['language'] ) { 115 | $translation['status'] = 'active'; 116 | } 117 | 118 | $update = wp_list_filter( $updates, array( 'language' => $translation['language'] ) ); 119 | if ( $update ) { 120 | $translation['update'] = 'available'; 121 | } else { 122 | $translation['update'] = 'none'; 123 | } 124 | 125 | return $translation; 126 | }, 127 | $translations 128 | ); 129 | 130 | foreach ( $translations as $key => $translation ) { 131 | foreach ( array_keys( $translation ) as $field ) { 132 | if ( isset( $assoc_args[ $field ] ) && ! in_array( $translation[ $field ], array_map( 'trim', explode( ',', $assoc_args[ $field ] ) ), true ) ) { 133 | unset( $translations[ $key ] ); 134 | } 135 | } 136 | } 137 | 138 | $formatter = $this->get_formatter( $assoc_args ); 139 | $formatter->display_items( $translations ); 140 | } 141 | 142 | /** 143 | * Checks if a given language is installed. 144 | * 145 | * Returns exit code 0 when installed, 1 when uninstalled. 146 | * 147 | * ## OPTIONS 148 | * 149 | * 150 | * : The language code to check. 151 | * 152 | * ## EXAMPLES 153 | * 154 | * # Check whether the German language is installed; exit status 0 if installed, otherwise 1. 155 | * $ wp language core is-installed de_DE 156 | * $ echo $? 157 | * 1 158 | * 159 | * @subcommand is-installed 160 | * 161 | * @param array{string} $args Positional arguments. 162 | */ 163 | public function is_installed( $args ) { 164 | list( $language_code ) = $args; 165 | $available = $this->get_installed_languages(); 166 | if ( in_array( $language_code, $available, true ) ) { 167 | \WP_CLI::halt( 0 ); 168 | } else { 169 | \WP_CLI::halt( 1 ); 170 | } 171 | } 172 | 173 | /** 174 | * Installs a given language. 175 | * 176 | * Downloads the language pack from WordPress.org. Find your language code at: https://translate.wordpress.org/ 177 | * 178 | * ## OPTIONS 179 | * 180 | * ... 181 | * : Language code to install. 182 | * 183 | * [--activate] 184 | * : If set, the language will be activated immediately after install. 185 | * 186 | * ## EXAMPLES 187 | * 188 | * # Install the Brazilian Portuguese language. 189 | * $ wp language core install pt_BR 190 | * Downloading translation from https://downloads.wordpress.org/translation/core/6.5/pt_BR.zip... 191 | * Unpacking the update... 192 | * Installing the latest version... 193 | * Removing the old version of the translation... 194 | * Translation updated successfully. 195 | * Language 'pt_BR' installed. 196 | * Success: Installed 1 of 1 languages. 197 | * 198 | * @subcommand install 199 | * 200 | * @param string[] $args Positional arguments. 201 | * @param array{activate?: bool} $assoc_args Associative arguments. 202 | */ 203 | public function install( $args, $assoc_args ) { 204 | $language_codes = (array) $args; 205 | $count = count( $language_codes ); 206 | 207 | if ( $count > 1 && in_array( true, $assoc_args, true ) ) { 208 | WP_CLI::error( 'Only a single language can be active.' ); 209 | } 210 | 211 | $available = $this->get_installed_languages(); 212 | 213 | $successes = 0; 214 | $errors = 0; 215 | $skips = 0; 216 | foreach ( $language_codes as $language_code ) { 217 | 218 | if ( in_array( $language_code, $available, true ) ) { 219 | \WP_CLI::log( "Language '{$language_code}' already installed." ); 220 | ++$skips; 221 | } else { 222 | $response = $this->download_language_pack( $language_code ); 223 | 224 | if ( is_wp_error( $response ) ) { 225 | \WP_CLI::warning( $response ); 226 | \WP_CLI::log( "Language '{$language_code}' not installed." ); 227 | 228 | // Skip if translation is not yet available. 229 | if ( 'not_found' === $response->get_error_code() ) { 230 | ++$skips; 231 | } else { 232 | ++$errors; 233 | } 234 | } else { 235 | \WP_CLI::log( "Language '{$language_code}' installed." ); 236 | ++$successes; 237 | } 238 | } 239 | 240 | if ( WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) { 241 | $this->activate_language( $language_code ); 242 | } 243 | } 244 | 245 | \WP_CLI\Utils\report_batch_operation_results( 'language', 'install', $count, $successes, $errors, $skips ); 246 | } 247 | 248 | /** 249 | * Uninstalls a given language. 250 | * 251 | * ## OPTIONS 252 | * 253 | * ... 254 | * : Language code to uninstall. 255 | * 256 | * ## EXAMPLES 257 | * 258 | * # Uninstall the Japanese core language pack. 259 | * $ wp language core uninstall ja 260 | * Success: Language uninstalled. 261 | * 262 | * @subcommand uninstall 263 | * @throws WP_CLI\ExitException 264 | * 265 | * @param string[] $args Positional arguments. 266 | */ 267 | public function uninstall( $args ) { 268 | global $wp_filesystem; 269 | 270 | $dir = 'core' === $this->obj_type ? '' : "/$this->obj_type"; 271 | $files = scandir( WP_LANG_DIR . $dir ); 272 | if ( ! $files ) { 273 | WP_CLI::error( 'No files found in language directory.' ); 274 | } 275 | 276 | $language_codes = (array) $args; 277 | 278 | $available = $this->get_installed_languages(); 279 | 280 | $current_locale = get_locale(); 281 | 282 | foreach ( $language_codes as $language_code ) { 283 | if ( ! in_array( $language_code, $available, true ) ) { 284 | WP_CLI::error( 'Language not installed.' ); 285 | } 286 | 287 | if ( $language_code === $current_locale ) { 288 | WP_CLI::warning( "The '{$language_code}' language is active." ); 289 | exit; 290 | } 291 | 292 | $files_to_remove = array( 293 | "$language_code.po", 294 | "$language_code.mo", 295 | "$language_code.l10n.php", 296 | "admin-$language_code.po", 297 | "admin-$language_code.mo", 298 | "admin-$language_code.l10n.php", 299 | "admin-network-$language_code.po", 300 | "admin-network-$language_code.mo", 301 | "admin-network-$language_code.l10n.php", 302 | "continents-cities-$language_code.po", 303 | "continents-cities-$language_code.mo", 304 | "continents-cities-$language_code.l10n.php", 305 | ); 306 | 307 | // As of WP 4.0, no API for deleting a language pack 308 | WP_Filesystem(); 309 | $deleted = false; 310 | foreach ( $files as $file ) { 311 | if ( '.' === $file[0] || is_dir( $file ) ) { 312 | continue; 313 | } 314 | 315 | if ( 316 | ! in_array( $file, $files_to_remove, true ) && 317 | ! preg_match( "/$language_code-\w{32}\.json/", $file ) 318 | ) { 319 | continue; 320 | } 321 | 322 | /** @var WP_Filesystem_Base $wp_filesystem */ 323 | $deleted = $wp_filesystem->delete( WP_LANG_DIR . $dir . '/' . $file ); 324 | } 325 | 326 | if ( $deleted ) { 327 | WP_CLI::success( 'Language uninstalled.' ); 328 | } else { 329 | WP_CLI::error( "Couldn't uninstall language." ); 330 | } 331 | } 332 | } 333 | 334 | /** 335 | * Updates installed languages for core. 336 | * 337 | * ## OPTIONS 338 | * 339 | * [--dry-run] 340 | * : Preview which translations would be updated. 341 | * 342 | * ## EXAMPLES 343 | * 344 | * # Update installed core languages packs. 345 | * $ wp language core update 346 | * Updating 'Japanese' translation for WordPress 6.4.3... 347 | * Downloading translation from https://downloads.wordpress.org/translation/core/6.4.3/ja.zip... 348 | * Translation updated successfully. 349 | * Success: Updated 1/1 translation. 350 | * 351 | * @subcommand update 352 | * 353 | * @param string[] $args Positional arguments. 354 | * @param array{'dry-run'?: bool} $assoc_args Associative arguments. 355 | */ 356 | public function update( $args, $assoc_args ) { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found -- Overruling the documentation, so not useless ;-). 357 | parent::update( $args, $assoc_args ); 358 | } 359 | 360 | /** 361 | * Activates a given language. 362 | * 363 | * **Warning: `wp language core activate` is deprecated. Use `wp site switch-language` instead.** 364 | * 365 | * ## OPTIONS 366 | * 367 | * 368 | * : Language code to activate. 369 | * 370 | * ## EXAMPLES 371 | * 372 | * # Activate the given language. 373 | * $ wp language core activate ja 374 | * Success: Language activated. 375 | * 376 | * @subcommand activate 377 | * @throws WP_CLI\ExitException 378 | * 379 | * @param array{string} $args Positional arguments. 380 | */ 381 | public function activate( $args ) { 382 | \WP_CLI::warning( 'This command is deprecated. use wp site switch-language instead' ); 383 | 384 | list( $language_code ) = $args; 385 | 386 | $this->activate_language( $language_code ); 387 | } 388 | 389 | private function activate_language( $language_code ) { 390 | $available = $this->get_installed_languages(); 391 | 392 | if ( ! in_array( $language_code, $available, true ) ) { 393 | WP_CLI::error( 'Language not installed.' ); 394 | } 395 | 396 | if ( 'en_US' === $language_code ) { 397 | $language_code = ''; 398 | } 399 | 400 | if ( get_locale() === $language_code ) { 401 | WP_CLI::warning( "Language '{$language_code}' already active." ); 402 | 403 | return; 404 | } 405 | 406 | update_option( 'WPLANG', $language_code ); 407 | 408 | WP_CLI::success( 'Language activated.' ); 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /src/Language_Namespace.php: -------------------------------------------------------------------------------- 1 | ...] 57 | * : One or more plugins to list languages for. 58 | * 59 | * [--all] 60 | * : If set, available languages for all plugins will be listed. 61 | * 62 | * [--field=] 63 | * : Display the value of a single field. 64 | * 65 | * [--=] 66 | * : Filter results by key=value pairs. 67 | * 68 | * [--fields=] 69 | * : Limit the output to specific fields. 70 | * 71 | * [--format=] 72 | * : Render output in a particular format. 73 | * --- 74 | * default: table 75 | * options: 76 | * - table 77 | * - csv 78 | * - json 79 | * - count 80 | * --- 81 | * 82 | * ## AVAILABLE FIELDS 83 | * 84 | * These fields will be displayed by default for each translation: 85 | * 86 | * * plugin 87 | * * language 88 | * * english_name 89 | * * native_name 90 | * * status 91 | * * update 92 | * * updated 93 | * 94 | * ## EXAMPLES 95 | * 96 | * # List available language packs for the plugin. 97 | * $ wp language plugin list hello-dolly --fields=language,english_name,status 98 | * +----------------+-------------------------+-------------+ 99 | * | language | english_name | status | 100 | * +----------------+-------------------------+-------------+ 101 | * | ar | Arabic | uninstalled | 102 | * | ary | Moroccan Arabic | uninstalled | 103 | * | az | Azerbaijani | uninstalled | 104 | * 105 | * @subcommand list 106 | * 107 | * @param string[] $args Positional arguments. 108 | * @param array{all?: bool, field?: string, format: string, plugin?: string, language?: string, english_name?: string, native_name?: string, status?: string, update?: string, updated?: string} $assoc_args Associative arguments. 109 | */ 110 | public function list_( $args, $assoc_args ) { 111 | $all = \WP_CLI\Utils\get_flag_value( $assoc_args, 'all', false ); 112 | 113 | if ( ! $all && empty( $args ) ) { 114 | WP_CLI::error( 'Please specify one or more plugins, or use --all.' ); 115 | } 116 | 117 | if ( $all ) { 118 | $args = array_map( '\WP_CLI\Utils\get_plugin_name', array_keys( $this->get_all_plugins() ) ); 119 | 120 | if ( empty( $args ) ) { 121 | WP_CLI::success( 'No plugins installed.' ); 122 | return; 123 | } 124 | } 125 | 126 | $updates = $this->get_translation_updates(); 127 | $current_locale = get_locale(); 128 | 129 | $translations = array(); 130 | $plugins = new \WP_CLI\Fetchers\Plugin(); 131 | 132 | foreach ( $args as $plugin ) { 133 | if ( ! $plugins->get( $plugin ) ) { 134 | WP_CLI::warning( "Plugin '{$plugin}' not found." ); 135 | continue; 136 | } 137 | 138 | $installed_translations = $this->get_installed_languages( $plugin ); 139 | $available_translations = $this->get_all_languages( $plugin ); 140 | 141 | foreach ( $available_translations as $translation ) { 142 | $translation['plugin'] = $plugin; 143 | $translation['status'] = in_array( $translation['language'], $installed_translations, true ) ? 'installed' : 'uninstalled'; 144 | 145 | if ( $current_locale === $translation['language'] ) { 146 | $translation['status'] = 'active'; 147 | } 148 | 149 | $filter_args = array( 150 | 'language' => $translation['language'], 151 | 'type' => 'plugin', 152 | 'slug' => $plugin, 153 | ); 154 | $update = wp_list_filter( $updates, $filter_args ); 155 | 156 | $translation['update'] = $update ? 'available' : 'none'; 157 | 158 | // Support features like --status=active. 159 | foreach ( array_keys( $translation ) as $field ) { 160 | if ( isset( $assoc_args[ $field ] ) && ! in_array( $translation[ $field ], array_map( 'trim', explode( ',', (string) $assoc_args[ $field ] ) ), true ) ) { 161 | continue 2; 162 | } 163 | } 164 | 165 | $translations[] = $translation; 166 | } 167 | } 168 | 169 | $formatter = $this->get_formatter( $assoc_args ); 170 | $formatter->display_items( $translations ); 171 | } 172 | 173 | /** 174 | * Checks if a given language is installed. 175 | * 176 | * Returns exit code 0 when installed, 1 when uninstalled. 177 | * 178 | * ## OPTIONS 179 | * 180 | * 181 | * : Plugin to check for. 182 | * 183 | * ... 184 | * : The language code to check. 185 | * 186 | * ## EXAMPLES 187 | * 188 | * # Check whether the German language is installed for Akismet; exit status 0 if installed, otherwise 1. 189 | * $ wp language plugin is-installed akismet de_DE 190 | * $ echo $? 191 | * 1 192 | * 193 | * @subcommand is-installed 194 | * 195 | * @param non-empty-array $args Positional arguments. 196 | */ 197 | public function is_installed( $args ) { 198 | $plugin = array_shift( $args ); 199 | $language_codes = $args; 200 | 201 | $available = $this->get_installed_languages( $plugin ); 202 | 203 | foreach ( $language_codes as $language_code ) { 204 | if ( ! in_array( $language_code, $available, true ) ) { 205 | \WP_CLI::halt( 1 ); 206 | } 207 | } 208 | 209 | \WP_CLI::halt( 0 ); 210 | } 211 | 212 | /** 213 | * Installs a given language for a plugin. 214 | * 215 | * Downloads the language pack from WordPress.org. 216 | * 217 | * ## OPTIONS 218 | * 219 | * [] 220 | * : Plugin to install language for. 221 | * 222 | * [--all] 223 | * : If set, languages for all plugins will be installed. 224 | * 225 | * ... 226 | * : Language code to install. 227 | * 228 | * [--format=] 229 | * : Render output in a particular format. Used when installing languages for all plugins. 230 | * --- 231 | * default: table 232 | * options: 233 | * - table 234 | * - csv 235 | * - json 236 | * - summary 237 | * --- 238 | * 239 | * ## EXAMPLES 240 | * 241 | * # Install the Japanese language for Akismet. 242 | * $ wp language plugin install akismet ja 243 | * Downloading translation from https://downloads.wordpress.org/translation/plugin/akismet/4.0.3/ja.zip... 244 | * Unpacking the update... 245 | * Installing the latest version... 246 | * Removing the old version of the translation... 247 | * Translation updated successfully. 248 | * Language 'ja' installed. 249 | * Success: Installed 1 of 1 languages. 250 | * 251 | * @subcommand install 252 | * 253 | * @param string[] $args Positional arguments. 254 | * @param array{all?: bool, format: string} $assoc_args Associative arguments. 255 | */ 256 | public function install( $args, $assoc_args ) { 257 | $all = \WP_CLI\Utils\get_flag_value( $assoc_args, 'all', false ); 258 | 259 | if ( ! $all && count( $args ) < 2 ) { 260 | \WP_CLI::error( 'Please specify a plugin, or use --all.' ); 261 | } 262 | 263 | if ( $all ) { 264 | $this->install_many( $args, $assoc_args ); 265 | } else { 266 | $this->install_one( $args, $assoc_args ); 267 | } 268 | } 269 | 270 | /** 271 | * Installs translations for a plugin. 272 | * 273 | * @param array $args Runtime arguments. 274 | * @param array $assoc_args Runtime arguments. 275 | */ 276 | private function install_one( $args, $assoc_args ) { 277 | $plugin = array_shift( $args ); 278 | $language_codes = $args; 279 | $count = count( $language_codes ); 280 | 281 | $available = $this->get_installed_languages( $plugin ); 282 | 283 | $successes = 0; 284 | $errors = 0; 285 | $skips = 0; 286 | foreach ( $language_codes as $language_code ) { 287 | 288 | if ( in_array( $language_code, $available, true ) ) { 289 | \WP_CLI::log( "Language '{$language_code}' already installed." ); 290 | ++$skips; 291 | } else { 292 | $response = $this->download_language_pack( $language_code, $plugin ); 293 | 294 | if ( is_wp_error( $response ) ) { 295 | \WP_CLI::warning( $response ); 296 | \WP_CLI::log( "Language '{$language_code}' not installed." ); 297 | 298 | // Skip if translation is not yet available. 299 | if ( 'not_found' === $response->get_error_code() ) { 300 | ++$skips; 301 | } else { 302 | ++$errors; 303 | } 304 | } else { 305 | \WP_CLI::log( "Language '{$language_code}' installed." ); 306 | ++$successes; 307 | } 308 | } 309 | } 310 | 311 | \WP_CLI\Utils\report_batch_operation_results( 'language', 'install', $count, $successes, $errors, $skips ); 312 | } 313 | 314 | /** 315 | * Installs translations for all installed plugins. 316 | * 317 | * @param array $args Runtime arguments. 318 | * @param array $assoc_args Runtime arguments. 319 | */ 320 | private function install_many( $args, $assoc_args ) { 321 | $language_codes = (array) $args; 322 | $plugins = $this->get_all_plugins(); 323 | 324 | if ( empty( $assoc_args['format'] ) ) { 325 | $assoc_args['format'] = 'table'; 326 | } 327 | 328 | if ( in_array( $assoc_args['format'], array( 'json', 'csv' ), true ) ) { 329 | $logger = new \WP_CLI\Loggers\Quiet(); 330 | \WP_CLI::set_logger( $logger ); 331 | } 332 | 333 | if ( empty( $plugins ) ) { 334 | \WP_CLI::success( 'No plugins installed.' ); 335 | return; 336 | } 337 | 338 | $count = count( $plugins ) * count( $language_codes ); 339 | 340 | $results = array(); 341 | 342 | $successes = 0; 343 | $errors = 0; 344 | $skips = 0; 345 | foreach ( $plugins as $plugin_path => $plugin_details ) { 346 | $plugin_name = \WP_CLI\Utils\get_plugin_name( $plugin_path ); 347 | 348 | $available = $this->get_installed_languages( $plugin_name ); 349 | 350 | foreach ( $language_codes as $language_code ) { 351 | $result = [ 352 | 'name' => $plugin_name, 353 | 'locale' => $language_code, 354 | ]; 355 | 356 | if ( in_array( $language_code, $available, true ) ) { 357 | \WP_CLI::log( "Language '{$language_code}' for '{$plugin_details['Name']}' already installed." ); 358 | $result['status'] = 'already installed'; 359 | ++$skips; 360 | } else { 361 | $response = $this->download_language_pack( $language_code, $plugin_name ); 362 | 363 | if ( is_wp_error( $response ) ) { 364 | \WP_CLI::warning( $response ); 365 | \WP_CLI::log( "Language '{$language_code}' for '{$plugin_details['Name']}' not installed." ); 366 | 367 | if ( 'not_found' === $response->get_error_code() ) { 368 | $result['status'] = 'not available'; 369 | ++$skips; 370 | } else { 371 | $result['status'] = 'not installed'; 372 | ++$errors; 373 | } 374 | } else { 375 | \WP_CLI::log( "Language '{$language_code}' for '{$plugin_details['Name']}' installed." ); 376 | $result['status'] = 'installed'; 377 | ++$successes; 378 | } 379 | } 380 | 381 | $results[] = (object) $result; 382 | } 383 | } 384 | 385 | if ( 'summary' !== $assoc_args['format'] ) { 386 | \WP_CLI\Utils\format_items( $assoc_args['format'], $results, array( 'name', 'locale', 'status' ) ); 387 | } 388 | 389 | \WP_CLI\Utils\report_batch_operation_results( 'language', 'install', $count, $successes, $errors, $skips ); 390 | } 391 | 392 | /** 393 | * Uninstalls a given language for a plugin. 394 | * 395 | * ## OPTIONS 396 | * 397 | * [] 398 | * : Plugin to uninstall language for. 399 | * 400 | * [--all] 401 | * : If set, languages for all plugins will be uninstalled. 402 | * 403 | * ... 404 | * : Language code to uninstall. 405 | * 406 | * [--format=] 407 | * : Render output in a particular format. Used when installing languages for all plugins. 408 | * --- 409 | * default: table 410 | * options: 411 | * - table 412 | * - csv 413 | * - json 414 | * - summary 415 | * --- 416 | * 417 | * ## EXAMPLES 418 | * 419 | * # Uninstall the Japanese plugin language pack for Hello Dolly. 420 | * $ wp language plugin uninstall hello-dolly ja 421 | * Language 'ja' for 'hello-dolly' uninstalled. 422 | * +-------------+--------+-------------+ 423 | * | name | locale | status | 424 | * +-------------+--------+-------------+ 425 | * | hello-dolly | ja | uninstalled | 426 | * +-------------+--------+-------------+ 427 | * Success: Uninstalled 1 of 1 languages. 428 | * 429 | * @subcommand uninstall 430 | * 431 | * @param string[] $args Positional arguments. 432 | * @param array{all?: bool, format: string} $assoc_args Associative arguments. 433 | */ 434 | public function uninstall( $args, $assoc_args ) { 435 | /** @var WP_Filesystem_Base $wp_filesystem */ 436 | global $wp_filesystem; 437 | 438 | if ( empty( $assoc_args['format'] ) ) { 439 | $assoc_args['format'] = 'table'; 440 | } 441 | 442 | if ( in_array( $assoc_args['format'], array( 'json', 'csv' ), true ) ) { 443 | $logger = new \WP_CLI\Loggers\Quiet(); 444 | \WP_CLI::set_logger( $logger ); 445 | } 446 | 447 | $all = \WP_CLI\Utils\get_flag_value( $assoc_args, 'all', false ); 448 | 449 | if ( ! $all && count( $args ) < 2 ) { 450 | \WP_CLI::error( 'Please specify one or more plugins, or use --all.' ); 451 | } 452 | 453 | if ( $all ) { 454 | $plugins = array_map( '\WP_CLI\Utils\get_plugin_name', array_keys( $this->get_all_plugins() ) ); 455 | 456 | if ( empty( $plugins ) ) { 457 | WP_CLI::success( 'No plugins installed.' ); 458 | return; 459 | } 460 | } else { 461 | $plugins = array( array_shift( $args ) ); 462 | } 463 | 464 | $language_codes = (array) $args; 465 | $current_locale = get_locale(); 466 | 467 | $dir = WP_LANG_DIR . "/$this->obj_type"; 468 | $files = scandir( $dir ); 469 | 470 | if ( ! $files ) { 471 | \WP_CLI::error( 'No files found in language directory.' ); 472 | } 473 | 474 | // As of WP 4.0, no API for deleting a language pack 475 | WP_Filesystem(); 476 | 477 | $count = count( $plugins ) * count( $language_codes ); 478 | 479 | $results = array(); 480 | 481 | $successes = 0; 482 | $errors = 0; 483 | $skips = 0; 484 | 485 | /** 486 | * @var string $plugin 487 | */ 488 | foreach ( $plugins as $plugin ) { 489 | $available = $this->get_installed_languages( $plugin ); 490 | 491 | foreach ( $language_codes as $language_code ) { 492 | $result = [ 493 | 'name' => $plugin, 494 | 'locale' => $language_code, 495 | 'status' => 'not available', 496 | ]; 497 | 498 | if ( ! in_array( $language_code, $available, true ) ) { 499 | $result['status'] = 'not installed'; 500 | \WP_CLI::warning( "Language '{$language_code}' not installed." ); 501 | if ( $all ) { 502 | ++$skips; 503 | } else { 504 | ++$errors; 505 | } 506 | $results[] = (object) $result; 507 | continue; 508 | } 509 | 510 | if ( $language_code === $current_locale ) { 511 | \WP_CLI::warning( "The '{$language_code}' language is active." ); 512 | exit; 513 | } 514 | 515 | $files_to_remove = array( 516 | "$plugin-$language_code.po", 517 | "$plugin-$language_code.mo", 518 | "$plugin-$language_code.l10n.php", 519 | ); 520 | 521 | $count_files_to_remove = 0; 522 | $count_files_removed = 0; 523 | $had_one_file = false; 524 | 525 | foreach ( $files as $file ) { 526 | if ( '.' === $file[0] || is_dir( $file ) ) { 527 | continue; 528 | } 529 | 530 | if ( 531 | ! in_array( $file, $files_to_remove, true ) && 532 | ! preg_match( "/$plugin-$language_code-\w{32}\.json/", $file ) 533 | ) { 534 | continue; 535 | } 536 | 537 | $had_one_file = true; 538 | 539 | ++$count_files_to_remove; 540 | 541 | if ( $wp_filesystem->delete( $dir . '/' . $file ) ) { 542 | ++$count_files_removed; 543 | } else { 544 | \WP_CLI::error( "Couldn't uninstall language: $language_code from plugin $plugin." ); 545 | } 546 | } 547 | 548 | if ( $count_files_to_remove === $count_files_removed ) { 549 | $result['status'] = 'uninstalled'; 550 | ++$successes; 551 | \WP_CLI::log( "Language '{$language_code}' for '{$plugin}' uninstalled." ); 552 | } elseif ( $count_files_removed ) { 553 | \WP_CLI::log( "Language '{$language_code}' for '{$plugin}' partially uninstalled." ); 554 | $result['status'] = 'partial uninstall'; 555 | ++$errors; 556 | } elseif ( $had_one_file ) { /* $count_files_removed == 0 */ 557 | \WP_CLI::log( "Couldn't uninstall language '{$language_code}' from plugin {$plugin}." ); 558 | $result['status'] = 'failed to uninstall'; 559 | ++$errors; 560 | } else { 561 | \WP_CLI::log( "Language '{$language_code}' for '{$plugin}' already uninstalled." ); 562 | $result['status'] = 'already uninstalled'; 563 | ++$skips; 564 | } 565 | 566 | $results[] = (object) $result; 567 | } 568 | } 569 | 570 | if ( 'summary' !== $assoc_args['format'] ) { 571 | \WP_CLI\Utils\format_items( $assoc_args['format'], $results, array( 'name', 'locale', 'status' ) ); 572 | } 573 | 574 | \WP_CLI\Utils\report_batch_operation_results( 'language', 'uninstall', $count, $successes, $errors, $skips ); 575 | } 576 | 577 | /** 578 | * Updates installed languages for one or more plugins. 579 | * 580 | * ## OPTIONS 581 | * 582 | * [...] 583 | * : One or more plugins to update languages for. 584 | * 585 | * [--all] 586 | * : If set, languages for all plugins will be updated. 587 | * 588 | * [--dry-run] 589 | * : Preview which translations would be updated. 590 | * 591 | * ## EXAMPLES 592 | * 593 | * # Update all installed language packs for all plugins. 594 | * $ wp language plugin update --all 595 | * Updating 'Japanese' translation for Akismet 3.1.11... 596 | * Downloading translation from https://downloads.wordpress.org/translation/plugin/akismet/3.1.11/ja.zip... 597 | * Translation updated successfully. 598 | * Success: Updated 1/1 translation. 599 | * 600 | * @subcommand update 601 | * 602 | * @param string[] $args Positional arguments. 603 | * @param array{'dry-run'?: bool, all?: bool} $assoc_args Associative arguments. 604 | */ 605 | public function update( $args, $assoc_args ) { 606 | $all = \WP_CLI\Utils\get_flag_value( $assoc_args, 'all', false ); 607 | 608 | if ( ! $all && empty( $args ) ) { 609 | WP_CLI::error( 'Please specify one or more plugins, or use --all.' ); 610 | } 611 | 612 | if ( $all ) { 613 | $args = array_map( '\WP_CLI\Utils\get_plugin_name', array_keys( $this->get_all_plugins() ) ); 614 | if ( empty( $args ) ) { 615 | WP_CLI::success( 'No plugins installed.' ); 616 | 617 | return; 618 | } 619 | } 620 | 621 | parent::update( $args, $assoc_args ); 622 | } 623 | 624 | /** 625 | * Gets all available plugins. 626 | * 627 | * Uses the same filter core uses in plugins.php to determine which plugins 628 | * should be available to manage through the WP_Plugins_List_Table class. 629 | * 630 | * @return array 631 | */ 632 | private function get_all_plugins() { 633 | // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Using WP native hook. 634 | return apply_filters( 'all_plugins', get_plugins() ); 635 | } 636 | } 637 | -------------------------------------------------------------------------------- /src/Site_Switch_Language_Command.php: -------------------------------------------------------------------------------- 1 | 12 | * : Language code to activate. 13 | * 14 | * ## EXAMPLES 15 | * 16 | * $ wp site switch-language ja 17 | * Success: Language activated. 18 | * 19 | * @throws WP_CLI\ExitException 20 | */ 21 | public function __invoke( $args, $assoc_args ) { 22 | list( $language_code ) = $args; 23 | 24 | $available = $this->get_installed_languages(); 25 | 26 | if ( ! in_array( $language_code, $available, true ) ) { 27 | WP_CLI::error( 'Language not installed.' ); 28 | } 29 | 30 | if ( 'en_US' === $language_code ) { 31 | $language_code = ''; 32 | } 33 | 34 | if ( get_locale() === $language_code ) { 35 | WP_CLI::warning( "Language '{$language_code}' already active." ); 36 | 37 | return; 38 | } 39 | 40 | update_option( 'WPLANG', $language_code ); 41 | 42 | WP_CLI::success( 'Language activated.' ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Theme_Language_Command.php: -------------------------------------------------------------------------------- 1 | ...] 57 | * : One or more themes to list languages for. 58 | * 59 | * [--all] 60 | * : If set, available languages for all themes will be listed. 61 | * 62 | * [--field=] 63 | * : Display the value of a single field. 64 | * 65 | * [--=] 66 | * : Filter results by key=value pairs. 67 | * 68 | * [--fields=] 69 | * : Limit the output to specific fields. 70 | * 71 | * [--format=] 72 | * : Render output in a particular format. 73 | * --- 74 | * default: table 75 | * options: 76 | * - table 77 | * - csv 78 | * - json 79 | * - count 80 | * --- 81 | * 82 | * ## AVAILABLE FIELDS 83 | * 84 | * These fields will be displayed by default for each translation: 85 | * 86 | * * theme 87 | * * language 88 | * * english_name 89 | * * native_name 90 | * * status 91 | * * update 92 | * * updated 93 | * 94 | * ## EXAMPLES 95 | * 96 | * # List available language packs for the theme. 97 | * $ wp language theme list twentyten --fields=language,english_name,status 98 | * +----------------+-------------------------+-------------+ 99 | * | language | english_name | status | 100 | * +----------------+-------------------------+-------------+ 101 | * | ar | Arabic | uninstalled | 102 | * | ary | Moroccan Arabic | uninstalled | 103 | * | az | Azerbaijani | uninstalled | 104 | * 105 | * @subcommand list 106 | * 107 | * @param string[] $args Positional arguments. 108 | * @param array{all?: bool, field?: string, format: string, theme?: string, language?: string, english_name?: string, native_name?: string, status?: string, update?: string, updated?: string} $assoc_args Associative arguments. 109 | */ 110 | public function list_( $args, $assoc_args ) { 111 | $all = \WP_CLI\Utils\get_flag_value( $assoc_args, 'all', false ); 112 | 113 | if ( ! $all && empty( $args ) ) { 114 | WP_CLI::error( 'Please specify one or more themes, or use --all.' ); 115 | } 116 | 117 | if ( $all ) { 118 | $args = array_map( 119 | function ( $file ) { 120 | return \WP_CLI\Utils\get_theme_name( $file ); 121 | }, 122 | array_keys( wp_get_themes() ) 123 | ); 124 | 125 | if ( empty( $args ) ) { 126 | WP_CLI::success( 'No themes installed.' ); 127 | return; 128 | } 129 | } 130 | 131 | $updates = $this->get_translation_updates(); 132 | $current_locale = get_locale(); 133 | 134 | $translations = array(); 135 | $themes = new \WP_CLI\Fetchers\Theme(); 136 | 137 | foreach ( $args as $theme ) { 138 | 139 | if ( ! $themes->get( $theme ) ) { 140 | WP_CLI::warning( "Theme '{$theme}' not found." ); 141 | continue; 142 | } 143 | 144 | $installed_translations = $this->get_installed_languages( $theme ); 145 | $available_translations = $this->get_all_languages( $theme ); 146 | 147 | foreach ( $available_translations as $translation ) { 148 | $translation['theme'] = $theme; 149 | $translation['status'] = in_array( $translation['language'], $installed_translations, true ) ? 'installed' : 'uninstalled'; 150 | 151 | if ( $current_locale === $translation['language'] ) { 152 | $translation['status'] = 'active'; 153 | } 154 | 155 | $filter_args = array( 156 | 'language' => $translation['language'], 157 | 'type' => 'theme', 158 | 'slug' => $theme, 159 | ); 160 | $update = wp_list_filter( $updates, $filter_args ); 161 | 162 | $translation['update'] = $update ? 'available' : 'none'; 163 | 164 | // Support features like --status=active. 165 | foreach ( array_keys( $translation ) as $field ) { 166 | if ( isset( $assoc_args[ $field ] ) && ! in_array( $translation[ $field ], array_map( 'trim', explode( ',', $assoc_args[ $field ] ) ), true ) ) { 167 | continue 2; 168 | } 169 | } 170 | 171 | $translations[] = $translation; 172 | } 173 | } 174 | 175 | $formatter = $this->get_formatter( $assoc_args ); 176 | $formatter->display_items( $translations ); 177 | } 178 | 179 | /** 180 | * Checks if a given language is installed. 181 | * 182 | * Returns exit code 0 when installed, 1 when uninstalled. 183 | * 184 | * ## OPTIONS 185 | * 186 | * 187 | * : Theme to check for. 188 | * 189 | * ... 190 | * : The language code to check. 191 | * 192 | * ## EXAMPLES 193 | * 194 | * # Check whether the German language is installed for Twenty Seventeen; exit status 0 if installed, otherwise 1. 195 | * $ wp language theme is-installed twentyseventeen de_DE 196 | * $ echo $? 197 | * 1 198 | * 199 | * @subcommand is-installed 200 | * 201 | * @param non-empty-array $args Positional arguments. 202 | */ 203 | public function is_installed( $args ) { 204 | $theme = array_shift( $args ); 205 | $language_codes = (array) $args; 206 | 207 | $available = $this->get_installed_languages( $theme ); 208 | 209 | foreach ( $language_codes as $language_code ) { 210 | if ( ! in_array( $language_code, $available, true ) ) { 211 | \WP_CLI::halt( 1 ); 212 | } 213 | } 214 | 215 | \WP_CLI::halt( 0 ); 216 | } 217 | 218 | /** 219 | * Installs a given language for a theme. 220 | * 221 | * Downloads the language pack from WordPress.org. 222 | * 223 | * ## OPTIONS 224 | * 225 | * [] 226 | * : Theme to install language for. 227 | * 228 | * [--all] 229 | * : If set, languages for all themes will be installed. 230 | * 231 | * ... 232 | * : Language code to install. 233 | * 234 | * [--format=] 235 | * : Render output in a particular format. Used when installing languages for all themes. 236 | * --- 237 | * default: table 238 | * options: 239 | * - table 240 | * - csv 241 | * - json 242 | * - summary 243 | * --- 244 | * 245 | * ## EXAMPLES 246 | * 247 | * # Install the Japanese language for Twenty Seventeen. 248 | * $ wp language theme install twentyseventeen ja 249 | * Downloading translation from https://downloads.wordpress.org/translation/theme/twentyseventeen/1.3/ja.zip... 250 | * Unpacking the update... 251 | * Installing the latest version... 252 | * Translation updated successfully. 253 | * Language 'ja' installed. 254 | * Success: Installed 1 of 1 languages. 255 | * 256 | * @subcommand install 257 | * 258 | * @param string[] $args Positional arguments. 259 | * @param array{all?: bool, format: string} $assoc_args Associative arguments. 260 | */ 261 | public function install( $args, $assoc_args ) { 262 | $all = \WP_CLI\Utils\get_flag_value( $assoc_args, 'all', false ); 263 | 264 | if ( ! $all && count( $args ) < 2 ) { 265 | \WP_CLI::error( 'Please specify a theme, or use --all.' ); 266 | } 267 | 268 | if ( $all ) { 269 | $this->install_many( $args, $assoc_args ); 270 | } else { 271 | $this->install_one( $args, $assoc_args ); 272 | } 273 | } 274 | 275 | /** 276 | * Installs translations for a theme. 277 | * 278 | * @param array $args Runtime arguments. 279 | * @param array $assoc_args Runtime arguments. 280 | */ 281 | private function install_one( $args, $assoc_args ) { 282 | $theme = array_shift( $args ); 283 | $language_codes = $args; 284 | $count = count( $language_codes ); 285 | 286 | $available = $this->get_installed_languages( $theme ); 287 | 288 | $successes = 0; 289 | $errors = 0; 290 | $skips = 0; 291 | foreach ( $language_codes as $language_code ) { 292 | 293 | if ( in_array( $language_code, $available, true ) ) { 294 | \WP_CLI::log( "Language '{$language_code}' already installed." ); 295 | ++$skips; 296 | } else { 297 | $response = $this->download_language_pack( $language_code, $theme ); 298 | 299 | if ( is_wp_error( $response ) ) { 300 | \WP_CLI::warning( $response ); 301 | \WP_CLI::log( "Language '{$language_code}' not installed." ); 302 | 303 | // Skip if translation is not yet available. 304 | if ( 'not_found' === $response->get_error_code() ) { 305 | ++$skips; 306 | } else { 307 | ++$errors; 308 | } 309 | } else { 310 | \WP_CLI::log( "Language '{$language_code}' installed." ); 311 | ++$successes; 312 | } 313 | } 314 | } 315 | \WP_CLI\Utils\report_batch_operation_results( 'language', 'install', $count, $successes, $errors, $skips ); 316 | } 317 | 318 | /** 319 | * Installs translations for all installed themes. 320 | * 321 | * @param array $args Runtime arguments. 322 | * @param array $assoc_args Runtime arguments. 323 | */ 324 | private function install_many( $args, $assoc_args ) { 325 | $language_codes = (array) $args; 326 | 327 | /** 328 | * @var \WP_Theme[] $themes 329 | */ 330 | $themes = wp_get_themes(); 331 | 332 | if ( empty( $assoc_args['format'] ) ) { 333 | $assoc_args['format'] = 'table'; 334 | } 335 | 336 | if ( in_array( $assoc_args['format'], array( 'json', 'csv' ), true ) ) { 337 | $logger = new \WP_CLI\Loggers\Quiet(); 338 | \WP_CLI::set_logger( $logger ); 339 | } 340 | 341 | if ( empty( $themes ) ) { 342 | \WP_CLI::success( 'No themes installed.' ); 343 | return; 344 | } 345 | 346 | $count = count( $themes ) * count( $language_codes ); 347 | 348 | $results = array(); 349 | 350 | $successes = 0; 351 | $errors = 0; 352 | $skips = 0; 353 | foreach ( $themes as $theme_path => $theme_details ) { 354 | $theme_name = \WP_CLI\Utils\get_theme_name( $theme_path ); 355 | 356 | $available = $this->get_installed_languages( $theme_name ); 357 | 358 | /** 359 | * @var string $display_name 360 | */ 361 | $display_name = $theme_details['Name']; 362 | 363 | foreach ( $language_codes as $language_code ) { 364 | $result = [ 365 | 'name' => $theme_name, 366 | 'locale' => $language_code, 367 | ]; 368 | 369 | if ( in_array( $language_code, $available, true ) ) { 370 | \WP_CLI::log( "Language '{$language_code}' for '{$display_name}' already installed." ); 371 | $result['status'] = 'already installed'; 372 | ++$skips; 373 | } else { 374 | $response = $this->download_language_pack( $language_code, $theme_name ); 375 | 376 | if ( is_wp_error( $response ) ) { 377 | \WP_CLI::warning( $response ); 378 | \WP_CLI::log( "Language '{$language_code}' for '{$display_name}' not installed." ); 379 | 380 | if ( 'not_found' === $response->get_error_code() ) { 381 | $result['status'] = 'not available'; 382 | ++$skips; 383 | } else { 384 | $result['status'] = 'not installed'; 385 | ++$errors; 386 | } 387 | } else { 388 | \WP_CLI::log( "Language '{$language_code}' for '{$display_name}' installed." ); 389 | $result['status'] = 'installed'; 390 | ++$successes; 391 | } 392 | } 393 | 394 | $results[] = (object) $result; 395 | } 396 | } 397 | 398 | if ( 'summary' !== $assoc_args['format'] ) { 399 | \WP_CLI\Utils\format_items( $assoc_args['format'], $results, array( 'name', 'locale', 'status' ) ); 400 | } 401 | 402 | \WP_CLI\Utils\report_batch_operation_results( 'language', 'install', $count, $successes, $errors, $skips ); 403 | } 404 | 405 | /** 406 | * Uninstalls a given language for a theme. 407 | * 408 | * ## OPTIONS 409 | * 410 | * [] 411 | * : Theme to uninstall language for. 412 | * 413 | * [--all] 414 | * : If set, languages for all themes will be uninstalled. 415 | * 416 | * ... 417 | * : Language code to uninstall. 418 | * 419 | * [--format=] 420 | * : Render output in a particular format. Used when installing languages for all themes. 421 | * --- 422 | * default: table 423 | * options: 424 | * - table 425 | * - csv 426 | * - json 427 | * - summary 428 | * --- 429 | * 430 | * ## EXAMPLES 431 | * 432 | * # Uninstall the Japanese theme language pack for Twenty Ten. 433 | * $ wp language theme uninstall twentyten ja 434 | * Language 'ja' for 'twentyten' uninstalled. 435 | * +-----------+--------+-------------+ 436 | * | name | locale | status | 437 | * +-----------+--------+-------------+ 438 | * | twentyten | ja | uninstalled | 439 | * +-----------+--------+-------------+ 440 | * Success: Uninstalled 1 of 1 languages. 441 | * 442 | * @subcommand uninstall 443 | * 444 | * @param string[] $args Positional arguments. 445 | * @param array{all?: bool, format: string} $assoc_args Associative arguments. 446 | */ 447 | public function uninstall( $args, $assoc_args ) { 448 | /** @var WP_Filesystem_Base $wp_filesystem */ 449 | global $wp_filesystem; 450 | 451 | if ( empty( $assoc_args['format'] ) ) { 452 | $assoc_args['format'] = 'table'; 453 | } 454 | 455 | if ( in_array( $assoc_args['format'], array( 'json', 'csv' ), true ) ) { 456 | $logger = new \WP_CLI\Loggers\Quiet(); 457 | \WP_CLI::set_logger( $logger ); 458 | } 459 | 460 | $all = \WP_CLI\Utils\get_flag_value( $assoc_args, 'all', false ); 461 | 462 | if ( ! $all && count( $args ) < 2 ) { 463 | \WP_CLI::error( 'Please specify one or more themes, or use --all.' ); 464 | } 465 | 466 | if ( $all ) { 467 | $themes = wp_get_themes(); 468 | 469 | if ( empty( $themes ) ) { 470 | \WP_CLI::success( 'No themes installed.' ); 471 | return; 472 | } 473 | 474 | $process_themes = array(); 475 | foreach ( $themes as $theme_path => $theme_details ) { 476 | $theme_name = \WP_CLI\Utils\get_theme_name( $theme_path ); 477 | array_push( $process_themes, $theme_name ); 478 | } 479 | } else { 480 | $process_themes = array( array_shift( $args ) ); 481 | } 482 | 483 | $language_codes = (array) $args; 484 | $current_locale = get_locale(); 485 | 486 | $dir = WP_LANG_DIR . "/$this->obj_type"; 487 | $files = scandir( $dir ); 488 | 489 | if ( ! $files ) { 490 | \WP_CLI::error( 'No files found in language directory.' ); 491 | } 492 | 493 | $count = count( $process_themes ) * count( $language_codes ); 494 | 495 | $results = array(); 496 | 497 | $successes = 0; 498 | $errors = 0; 499 | $skips = 0; 500 | 501 | // As of WP 4.0, no API for deleting a language pack 502 | WP_Filesystem(); 503 | 504 | /** 505 | * @var string $theme 506 | */ 507 | foreach ( $process_themes as $theme ) { 508 | $available_languages = $this->get_installed_languages( $theme ); 509 | 510 | foreach ( $language_codes as $language_code ) { 511 | $result = [ 512 | 'name' => $theme, 513 | 'locale' => $language_code, 514 | 'status' => 'not available', 515 | ]; 516 | 517 | if ( ! in_array( $language_code, $available_languages, true ) ) { 518 | $result['status'] = 'not installed'; 519 | \WP_CLI::warning( "Language '{$language_code}' not installed." ); 520 | if ( $all ) { 521 | ++$skips; 522 | } else { 523 | ++$errors; 524 | } 525 | $results[] = (object) $result; 526 | continue; 527 | } 528 | 529 | if ( $language_code === $current_locale ) { 530 | \WP_CLI::warning( "The '{$language_code}' language is active." ); 531 | exit; 532 | } 533 | 534 | $files_to_remove = array( 535 | "$theme-$language_code.po", 536 | "$theme-$language_code.mo", 537 | "$theme-$language_code.l10n.php", 538 | ); 539 | 540 | $count_files_to_remove = 0; 541 | $count_files_removed = 0; 542 | $had_one_file = false; 543 | 544 | foreach ( $files as $file ) { 545 | if ( '.' === $file[0] || is_dir( $file ) ) { 546 | continue; 547 | } 548 | 549 | if ( 550 | ! in_array( $file, $files_to_remove, true ) && 551 | ! preg_match( "/$theme-$language_code-\w{32}\.json/", $file ) 552 | ) { 553 | continue; 554 | } 555 | 556 | $had_one_file = true; 557 | 558 | ++$count_files_to_remove; 559 | 560 | if ( $wp_filesystem->delete( $dir . '/' . $file ) ) { 561 | ++$count_files_removed; 562 | } else { 563 | \WP_CLI::error( "Couldn't uninstall language: $language_code from theme $theme." ); 564 | } 565 | } 566 | 567 | if ( $count_files_to_remove === $count_files_removed ) { 568 | $result['status'] = 'uninstalled'; 569 | ++$successes; 570 | \WP_CLI::log( "Language '{$language_code}' for '{$theme}' uninstalled." ); 571 | } elseif ( $count_files_removed ) { 572 | \WP_CLI::log( "Language '{$language_code}' for '{$theme}' partially uninstalled." ); 573 | $result['status'] = 'partial uninstall'; 574 | ++$errors; 575 | } elseif ( $had_one_file ) { /* $count_files_removed == 0 */ 576 | \WP_CLI::log( "Couldn't uninstall language '{$language_code}' from theme {$theme}." ); 577 | $result['status'] = 'failed to uninstall'; 578 | ++$errors; 579 | } else { 580 | \WP_CLI::log( "Language '{$language_code}' for '{$theme}' already uninstalled." ); 581 | $result['status'] = 'already uninstalled'; 582 | ++$skips; 583 | } 584 | 585 | $results[] = (object) $result; 586 | } 587 | } 588 | 589 | if ( 'summary' !== $assoc_args['format'] ) { 590 | \WP_CLI\Utils\format_items( $assoc_args['format'], $results, array( 'name', 'locale', 'status' ) ); 591 | } 592 | 593 | \WP_CLI\Utils\report_batch_operation_results( 'language', 'uninstall', $count, $successes, $errors, $skips ); 594 | } 595 | 596 | /** 597 | * Updates installed languages for one or more themes. 598 | * 599 | * ## OPTIONS 600 | * 601 | * [...] 602 | * : One or more themes to update languages for. 603 | * 604 | * [--all] 605 | * : If set, languages for all themes will be updated. 606 | * 607 | * [--dry-run] 608 | * : Preview which translations would be updated. 609 | * 610 | * ## EXAMPLES 611 | * 612 | * # Update all installed language packs for all themes. 613 | * $ wp language theme update --all 614 | * Updating 'Japanese' translation for Twenty Fifteen 1.5... 615 | * Downloading translation from https://downloads.wordpress.org/translation/theme/twentyfifteen/1.5/ja.zip... 616 | * Translation updated successfully. 617 | * Success: Updated 1/1 translation. 618 | * 619 | * @subcommand update 620 | * 621 | * @param string[] $args Positional arguments. 622 | * @param array{'dry-run'?: bool, all?: bool} $assoc_args Associative arguments. 623 | */ 624 | public function update( $args, $assoc_args ) { 625 | $all = \WP_CLI\Utils\get_flag_value( $assoc_args, 'all', false ); 626 | 627 | if ( ! $all && empty( $args ) ) { 628 | WP_CLI::error( 'Please specify one or more themes, or use --all.' ); 629 | } 630 | 631 | if ( $all ) { 632 | $args = array_map( '\WP_CLI\Utils\get_theme_name', array_keys( wp_get_themes() ) ); 633 | if ( empty( $args ) ) { 634 | WP_CLI::success( 'No themes installed.' ); 635 | 636 | return; 637 | } 638 | } 639 | 640 | parent::update( $args, $assoc_args ); 641 | } 642 | } 643 | -------------------------------------------------------------------------------- /src/WP_CLI/CommandWithTranslation.php: -------------------------------------------------------------------------------- 1 | get_translation_updates(); 33 | 34 | if ( empty( $updates ) ) { 35 | WP_CLI::success( 'Translations are up to date.' ); 36 | 37 | return; 38 | } 39 | 40 | if ( empty( $args ) ) { 41 | $args = array( null ); // Used for core. 42 | } 43 | 44 | $upgrader = 'WP_CLI\\LanguagePackUpgrader'; 45 | $results = array(); 46 | $num_to_update = 0; 47 | 48 | foreach ( $args as $slug ) { 49 | // Gets a list of all languages. 50 | $all_languages = $this->get_all_languages( $slug ); 51 | 52 | $updates_per_type = array(); 53 | 54 | // Formats the updates list. 55 | foreach ( $updates as $update ) { 56 | if ( null !== $slug && $update->slug !== $slug ) { 57 | continue; 58 | } 59 | 60 | $name = 'WordPress'; // Core. 61 | 62 | if ( 'plugin' === $update->type ) { 63 | /** 64 | * @var array $plugins 65 | */ 66 | $plugins = get_plugins( '/' . $update->slug ); 67 | 68 | /** 69 | * @var array{Name: string}> $plugin_data 70 | */ 71 | $plugin_data = array_shift( $plugins ); 72 | $name = $plugin_data['Name']; 73 | } elseif ( 'theme' === $update->type ) { 74 | $theme_data = wp_get_theme( $update->slug ); 75 | /** 76 | * @var string $name 77 | */ 78 | $name = $theme_data['Name']; 79 | } 80 | 81 | // Gets the translation data. 82 | $translation = wp_list_filter( $all_languages, array( 'language' => $update->language ) ); 83 | $translation = (object) reset( $translation ); 84 | 85 | $update->Type = ucfirst( $update->type ); 86 | $update->Name = $name; 87 | $update->Version = $update->version; 88 | 89 | if ( isset( $translation->english_name ) ) { 90 | $update->Language = $translation->english_name; 91 | } 92 | 93 | if ( ! isset( $updates_per_type[ $update->type ] ) ) { 94 | $updates_per_type[ $update->type ] = array(); 95 | } 96 | $updates_per_type[ $update->type ][] = $update; 97 | } 98 | 99 | $obj_type = rtrim( $this->obj_type, 's' ); 100 | $available_updates = isset( $updates_per_type[ $obj_type ] ) ? $updates_per_type[ $obj_type ] : null; 101 | 102 | if ( ! is_array( $available_updates ) ) { 103 | continue; 104 | } 105 | 106 | $num_to_update += count( $available_updates ); 107 | 108 | if ( ! Utils\get_flag_value( $assoc_args, 'dry-run' ) ) { 109 | // Update translations. 110 | foreach ( $available_updates as $update ) { 111 | WP_CLI::line( "Updating '{$update->Language}' translation for {$update->Name} {$update->Version}..." ); 112 | 113 | /** 114 | * @var \WP_CLI\LanguagePackUpgrader $upgrader_instance 115 | */ 116 | $upgrader_instance = Utils\get_upgrader( $upgrader ); 117 | 118 | $result = $upgrader_instance->upgrade( $update ); 119 | 120 | $results[] = $result; 121 | } 122 | } 123 | } 124 | 125 | // Only preview which translations would be updated. 126 | if ( Utils\get_flag_value( $assoc_args, 'dry-run' ) ) { 127 | $update_count = count( $updates ); 128 | 129 | WP_CLI::line( 130 | sprintf( 131 | 'Found %d translation %s that would be processed:', 132 | $update_count, 133 | WP_CLI\Utils\pluralize( 'update', $update_count ) 134 | ) 135 | ); 136 | 137 | Utils\format_items( 'table', $updates, array( 'Type', 'Name', 'Version', 'Language' ) ); 138 | 139 | return; 140 | } 141 | 142 | $num_updated = count( array_filter( $results ) ); 143 | 144 | $line = sprintf( "Updated $num_updated/$num_to_update %s.", WP_CLI\Utils\pluralize( 'translation', $num_updated ) ); 145 | 146 | if ( $num_to_update === $num_updated ) { 147 | WP_CLI::success( $line ); 148 | } elseif ( $num_updated > 0 ) { 149 | WP_CLI::warning( $line ); 150 | } else { 151 | WP_CLI::error( $line ); 152 | } 153 | } 154 | 155 | /** 156 | * Get all updates available for all translations. 157 | * 158 | * @see wp_get_translation_updates() 159 | * 160 | * @return array 161 | */ 162 | protected function get_translation_updates() { 163 | $available = $this->get_installed_languages(); 164 | 165 | $func = function () use ( $available ) { 166 | return $available; 167 | }; 168 | 169 | switch ( $this->obj_type ) { 170 | case 'plugins': 171 | add_filter( 'plugins_update_check_locales', $func ); 172 | 173 | wp_clean_plugins_cache(); 174 | // Check for Plugin translation updates. 175 | wp_update_plugins(); 176 | 177 | remove_filter( 'plugins_update_check_locales', $func ); 178 | 179 | $transient = 'update_plugins'; 180 | break; 181 | case 'themes': 182 | add_filter( 'themes_update_check_locales', $func ); 183 | 184 | wp_clean_themes_cache(); 185 | // Check for Theme translation updates. 186 | wp_update_themes(); 187 | 188 | remove_filter( 'themes_update_check_locales', $func ); 189 | 190 | $transient = 'update_themes'; 191 | break; 192 | default: 193 | delete_site_transient( 'update_core' ); 194 | 195 | // Check for Core translation updates. 196 | wp_version_check(); 197 | 198 | $transient = 'update_core'; 199 | break; 200 | } 201 | 202 | $updates = array(); 203 | 204 | /** 205 | * @var object{translations: array} $transient 206 | */ 207 | $transient = get_site_transient( $transient ); 208 | 209 | if ( empty( $transient->translations ) ) { 210 | return $updates; 211 | } 212 | 213 | foreach ( $transient->translations as $translation ) { 214 | $updates[] = (object) $translation; 215 | } 216 | 217 | return $updates; 218 | } 219 | 220 | /** 221 | * Download a language pack. 222 | * 223 | * @see wp_download_language_pack() 224 | * 225 | * @param string $download Language code to download. 226 | * @param string $slug Plugin or theme slug. Not used for core. 227 | * @return string|\WP_Error Returns the language code if successfully downloaded, or a WP_Error object on failure. 228 | */ 229 | protected function download_language_pack( $download, $slug = null ) { 230 | $translations = $this->get_all_languages( $slug ); 231 | $translation_to_load = null; 232 | 233 | foreach ( $translations as $translation ) { 234 | if ( $translation['language'] === $download ) { 235 | $translation_to_load = $translation; 236 | break; 237 | } 238 | } 239 | 240 | if ( ! $translation_to_load ) { 241 | return new \WP_Error( 'not_found', $slug ? "Language '{$download}' for '{$slug}' not available." : "Language '{$download}' not available." ); 242 | } 243 | 244 | $translation = (object) $translation_to_load; 245 | 246 | $translation->type = rtrim( $this->obj_type, 's' ); 247 | 248 | // Make sure caching in LanguagePackUpgrader works. 249 | if ( ! isset( $translation->slug ) ) { 250 | $translation->slug = $slug; 251 | } 252 | 253 | $upgrader = 'WP_CLI\\LanguagePackUpgrader'; 254 | 255 | /** 256 | * @var \WP_CLI\LanguagePackUpgrader $upgrader_instance 257 | */ 258 | $upgrader_instance = Utils\get_upgrader( $upgrader ); 259 | 260 | // Incorrect docblock in WordPress core. 261 | // @phpstan-ignore argument.type 262 | $result = $upgrader_instance->upgrade( $translation, array( 'clear_update_cache' => false ) ); 263 | 264 | if ( is_wp_error( $result ) ) { 265 | return $result; 266 | } 267 | 268 | if ( ! $result ) { 269 | return new \WP_Error( 'not_installed', $slug ? "Could not install language '{$download}' for '{$slug}'." : "Could not install language '{$download}'." ); 270 | } 271 | 272 | return $translation->language; 273 | } 274 | 275 | /** 276 | * Return a list of installed languages. 277 | * 278 | * @param string $slug Optional. Plugin or theme slug. Defaults to 'default' for core. 279 | * 280 | * @return string[] 281 | */ 282 | protected function get_installed_languages( $slug = 'default' ) { 283 | /** 284 | * @var array>> $available 285 | */ 286 | $available = wp_get_installed_translations( $this->obj_type ); 287 | $available = ! empty( $available[ $slug ] ) ? array_keys( $available[ $slug ] ) : array(); 288 | $available[] = 'en_US'; 289 | 290 | return $available; 291 | } 292 | 293 | /** 294 | * Return a list of all languages. 295 | * 296 | * @param string $slug Optional. Plugin or theme slug. Not used for core. 297 | * 298 | * @return array 299 | */ 300 | protected function get_all_languages( $slug = null ) { 301 | require_once ABSPATH . '/wp-admin/includes/translation-install.php'; 302 | require ABSPATH . WPINC . '/version.php'; // Include an unmodified $wp_version 303 | 304 | $args = array( 305 | // False positive, because it's defined in version.php 306 | // @phpstan-ignore variable.undefined 307 | 'version' => $wp_version, 308 | ); 309 | 310 | if ( $slug ) { 311 | $args['slug'] = $slug; 312 | 313 | if ( 'plugins' === $this->obj_type ) { 314 | $plugins = get_plugins( '/' . $slug ); 315 | $plugin_data = array_shift( $plugins ); 316 | if ( isset( $plugin_data['Version'] ) ) { 317 | $args['version'] = $plugin_data['Version']; 318 | } 319 | } elseif ( 'themes' === $this->obj_type ) { 320 | $theme_data = wp_get_theme( $slug ); 321 | if ( isset( $theme_data['Version'] ) ) { 322 | $args['version'] = $theme_data['Version']; 323 | } 324 | } 325 | } 326 | 327 | $response = translations_api( $this->obj_type, $args ); 328 | 329 | if ( is_wp_error( $response ) ) { 330 | WP_CLI::error( $response ); 331 | } 332 | 333 | $translations = ! empty( $response['translations'] ) ? $response['translations'] : array(); 334 | 335 | $en_us = array( 336 | 'language' => 'en_US', 337 | 'english_name' => 'English (United States)', 338 | 'native_name' => 'English (United States)', 339 | 'updated' => '', 340 | ); 341 | 342 | $translations[] = $en_us; 343 | 344 | uasort( $translations, array( $this, 'sort_translations_callback' ) ); 345 | 346 | return $translations; 347 | } 348 | 349 | /** 350 | * Get Formatter object based on supplied parameters. 351 | * 352 | * @param array $assoc_args Parameters passed to command. Determines formatting. 353 | * @return Formatter 354 | */ 355 | protected function get_formatter( &$assoc_args ) { 356 | return new Formatter( $assoc_args, $this->obj_fields, $this->obj_type ); 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /src/WP_CLI/LanguagePackUpgrader.php: -------------------------------------------------------------------------------- 1 | strings['no_package'] ); 90 | } 91 | 92 | $language_update = $this->skin->language_update; 93 | $type = $language_update->type; 94 | $slug = empty( $language_update->slug ) ? 'default' : $language_update->slug; 95 | $updated = strtotime( $language_update->updated ); 96 | $version = $language_update->version; 97 | $language = $language_update->language; 98 | $ext = pathinfo( $package, PATHINFO_EXTENSION ); 99 | 100 | $temp = \WP_CLI\Utils\get_temp_dir() . uniqid( 'wp_' ) . '.' . $ext; 101 | 102 | $cache = WP_CLI::get_cache(); 103 | $cache_key = "translation/{$type}-{$slug}-{$version}-{$language}-{$updated}.{$ext}"; 104 | 105 | /** 106 | * @var string|false $cache_file 107 | */ 108 | $cache_file = $cache->has( $cache_key ); 109 | 110 | if ( $cache_file ) { 111 | WP_CLI::log( "Using cached file '$cache_file'..." ); 112 | copy( $cache_file, $temp ); 113 | return $temp; 114 | } 115 | 116 | $this->skin->feedback( 'downloading_package', $package ); 117 | 118 | $temp = download_url( $package, 600 ); // 10 minutes ought to be enough for everybody. 119 | 120 | if ( is_wp_error( $temp ) ) { 121 | return $temp; 122 | } 123 | 124 | $cache->import( $cache_key, $temp ); 125 | 126 | return $temp; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /wp-cli.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - language-command.php 3 | --------------------------------------------------------------------------------