From 3493e894bad7a083df186b15b55de97e7c2f5367 Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Tue, 6 Apr 2021 17:09:31 -0700 Subject: [PATCH] Generate translatable strings for non-static front end UI (#205) This adds a script that retrieves taxonomy term names and descriptions from Learn WP via the REST API and writes them to a file that can get picked up by the pot file generator. This way the terms will get imported into the learn-wordpress translation project and can get translated by community volunteers. This new script will get run by a scheduled GitHub action and committed directly back to this repo. The generated file is outside of the wp-content directory, so it should never end up being committed to SVN or otherwise make an appearance on the production server. This also adds the filter to dynamically translate term names and descriptions when they are output on the front end, and adds a notice on term edit screens so users know that the strings will get translated. Refs #187 --- .github/workflows/i18n.yml | 34 ++++ bin/i18n.php | 160 ++++++++++++++++++ composer.json | 3 +- composer.lock | 67 +++++++- extra/translation-strings.php | 86 ++++++++++ wp-content/plugins/wporg-learn/inc/admin.php | 57 +++++++ wp-content/plugins/wporg-learn/inc/i18n.php | 104 ++++++++++++ .../plugins/wporg-learn/wporg-learn.php | 2 + 8 files changed, 505 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/i18n.yml create mode 100755 bin/i18n.php create mode 100644 extra/translation-strings.php create mode 100644 wp-content/plugins/wporg-learn/inc/admin.php create mode 100644 wp-content/plugins/wporg-learn/inc/i18n.php diff --git a/.github/workflows/i18n.yml b/.github/workflows/i18n.yml new file mode 100644 index 000000000..ebd28dd7e --- /dev/null +++ b/.github/workflows/i18n.yml @@ -0,0 +1,34 @@ +name: I18n + +on: + schedule: + - cron: '0 6,18 * * *' + +jobs: + translation-strings: + name: Translation strings + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set PHP version + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + tools: composer:v2 + + - name: Install dependencies + run: composer install + + - name: Run translation script + run: php ./bin/i18n.php + + - name: Commit and push + # Using a specific hash here instead of a tagged version, for risk mitigation, since this action modifies our repo. + uses: actions-js/push@4decc2887d2770f29177082be3c8b04d342f5b64 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: trunk + message: Update translation strings diff --git a/bin/i18n.php b/bin/i18n.php new file mode 100755 index 000000000..f45eae5a7 --- /dev/null +++ b/bin/i18n.php @@ -0,0 +1,160 @@ +#!/usr/bin/php +status_code ) { + die( 'Could not retrieve taxonomy data.' ); + } + + $taxonomies = json_decode( $response->body, true ); + + if ( ! is_array( $taxonomies ) ) { + die( 'Taxonomies request returned unexpected data.' ); + } + + if ( count( $valid_post_types ) > 0 ) { + $taxonomies = array_filter( + $taxonomies, + function( $tax ) use ( $valid_post_types ) { + $supported_types = $tax['types']; + $matches = array_intersect( $supported_types, $valid_post_types ); + + return count( $matches ) > 0; + } + ); + } + + return $taxonomies; +} + +/** + * Get data about a taxonomy's terms from a REST API endpoint. + * + * @param string $taxonomy + * + * @return array + */ +function get_taxonomy_terms( $taxonomy ) { + $endpoint = ENDPOINT_BASE . $taxonomy . '?per_page=100'; + + $response = Requests::get( $endpoint ); + + if ( 200 !== $response->status_code ) { + die( sprintf( + 'Could not retrieve terms for %s.', + $taxonomy // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ) ); + } + + $terms = json_decode( $response->body, true ); + + if ( ! is_array( $terms ) ) { + die( sprintf( + 'Terms request for %s returned unexpected data.', + $taxonomy // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ) ); + } + + return $terms; +} + +/** + * Run the script. + */ +function main() { + if ( 'cli' === php_sapi_name() ) { + echo "\n"; + echo "Retrieving taxonomies...\n"; + } + + $valid_post_types = array( + 'lesson-plan', + 'wporg_workshop', + 'course', + 'lesson', + ); + $taxonomies = get_taxonomies( $valid_post_types ); + + if ( 'cli' === php_sapi_name() ) { + echo "Retrieving terms...\n"; + echo "\n"; + } + + $terms_by_tax = array(); + foreach ( $taxonomies as $taxonomy ) { + $terms = get_taxonomy_terms( $taxonomy['slug'] ); + + if ( count( $terms ) > 0 ) { + $terms_by_tax[ $taxonomy['name'] ] = $terms; + } + + unset( $terms ); + } + + $file_content = ''; + foreach ( $terms_by_tax as $tax_label => $terms ) { + $label = addcslashes( $tax_label, "'" ); + + foreach ( $terms as $term ) { + $name = addcslashes( $term['name'], "'" ); + $file_content .= "_x( '{$name}', '$label term name', 'wporg-learn' );\n"; + + if ( 'cli' === php_sapi_name() ) { + echo "$name\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + if ( $term['description'] ) { + $description = addcslashes( $term['description'], "'" ); + $file_content .= "_x( '{$description}', '$label term description', 'wporg-learn' );\n"; + } + } + } + + $path = dirname( __DIR__ ) . '/extra'; + if ( ! is_readable( $path ) ) { + mkdir( $path ); + } + + $file_name = 'translation-strings.php'; + $file_header = <<
=5.2" + }, + "require-dev": { + "requests/test-server": "dev-master" + }, + "type": "library", + "autoload": { + "psr-0": { + "Requests": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Ryan McCue", + "homepage": "http://ryanmccue.info" + } + ], + "description": "A HTTP library written in PHP, for human beings.", + "homepage": "http://github.com/rmccue/Requests", + "keywords": [ + "curl", + "fsockopen", + "http", + "idna", + "ipv6", + "iri", + "sockets" + ], + "support": { + "issues": "https://github.com/rmccue/Requests/issues", + "source": "https://github.com/rmccue/Requests/tree/master" + }, + "time": "2016-10-13T00:11:37+00:00" + }, { "name": "squizlabs/php_codesniffer", "version": "3.5.8", diff --git a/extra/translation-strings.php b/extra/translation-strings.php new file mode 100644 index 000000000..052b7d87d --- /dev/null +++ b/extra/translation-strings.php @@ -0,0 +1,86 @@ +set up a WordPress.org profile in order to contribute.', 'Workshop Series term description', 'wporg-learn' ); +_x( 'Developing for the Block Editor', 'Workshop Series term name', 'wporg-learn' ); +_x( 'This series is aimed at WordPress developers interested in developing for the block editor.', 'Workshop Series term description', 'wporg-learn' ); +_x( 'Diverse Speaker Training', 'Workshop Series term name', 'wporg-learn' ); +_x( 'This series is for people from marginalized or underrepresented groups who are thinking about speaking at WordPress events. You do not need to have any experience in public speaking, and this series is for all levels of experience. Topics include finding a talk topic, writing a pitch, creating the talk, becoming a better speaker, creating great slides, handling tough questions, and more!', 'Workshop Series term description', 'wporg-learn' ); +_x( 'Editor de Bloques - Tutorial básico', 'Workshop Series term name', 'wporg-learn' ); +_x( 'En esta serio de tutoriales, o WorkShops, se abordan los conceptos básico, y no tanto, para poder adentrarnos en el fascinante mundo del desarrollo en el editor de bloques de WordPress, también conocido como Gutenberg. +Partiendo de lo indispensable, iremos viendo sección tras sección, video tras video, cómo poder implementar nuestras propias ideas, necesidades, requerimientos, etc. de una manera simple y al alcance de todo el mundo.', 'Workshop Series term description', 'wporg-learn' ); +_x( 'Organizing WordPress Meetups', 'Workshop Series term name', 'wporg-learn' ); +_x( 'This series aimed at all WordPress Community members across the world that are interested in organizing WordPress meetups. You will learn how to organize a local WordPress Meetup, the important things to keep in mind while organizing a meetup, and how to keep your local meetup group active.', 'Workshop Series term description', 'wporg-learn' ); +_x( 'Seguridad en WordPress', 'Workshop Series term name', 'wporg-learn' ); +_x( 'Using the Block Editor', 'Workshop Series term name', 'wporg-learn' ); +_x( 'Learn how to get the most out of the WordPress block editor when publishing your content.', 'Workshop Series term description', 'wporg-learn' ); +_x( 'WordPress の基本', 'Workshop Series term name', 'wporg-learn' ); +_x( 'WordPress for Kids', 'Workshop Series term name', 'wporg-learn' ); +_x( 'WordPress Troubleshooting', 'Workshop Series term name', 'wporg-learn' ); +_x( 'Learn how to troubleshoot WordPress issues.', 'Workshop Series term description', 'wporg-learn' ); +_x( 'Block Development', 'Topics term name', 'wporg-learn' ); +_x( 'Block Editor', 'Topics term name', 'wporg-learn' ); +_x( 'Community Team', 'Topics term name', 'wporg-learn' ); +_x( 'Contributing', 'Topics term name', 'wporg-learn' ); +_x( 'Core', 'Topics term name', 'wporg-learn' ); +_x( 'CSS', 'Topics term name', 'wporg-learn' ); +_x( 'Diversity', 'Topics term name', 'wporg-learn' ); +_x( 'eCommerce', 'Topics term name', 'wporg-learn' ); +_x( 'Gutenberg', 'Topics term name', 'wporg-learn' ); +_x( 'Hosting', 'Topics term name', 'wporg-learn' ); +_x( 'Meetups', 'Topics term name', 'wporg-learn' ); +_x( 'Open-Source', 'Topics term name', 'wporg-learn' ); +_x( 'Publishing', 'Topics term name', 'wporg-learn' ); +_x( 'Security', 'Topics term name', 'wporg-learn' ); +_x( 'Translation', 'Topics term name', 'wporg-learn' ); +_x( 'Troubleshooting', 'Topics term name', 'wporg-learn' ); +_x( 'Sessions on WordPress Troubleshooting.', 'Topics term description', 'wporg-learn' ); +_x( 'UI', 'Topics term name', 'wporg-learn' ); +_x( 'WordPress', 'Topics term name', 'wporg-learn' ); diff --git a/wp-content/plugins/wporg-learn/inc/admin.php b/wp-content/plugins/wporg-learn/inc/admin.php new file mode 100644 index 000000000..3e16263ab --- /dev/null +++ b/wp-content/plugins/wporg-learn/inc/admin.php @@ -0,0 +1,57 @@ + +
+

+ translation project. Once you have added or changed a term\'s name or description, it may take up to 24 hours before it is available for translation. + ', 'wporg-learn' ) ), + esc_html( $labels->name ), + 'https://translate.wordpress.org/projects/meta/learn-wordpress/' + ); + ?> +

+
+ object_type; + + if ( count( array_intersect( $supported_types, $valid_post_types ) ) < 1 ) { + return $term; + } + + if ( $term instanceof WP_Term ) { + $term->name = esc_html( translate_with_gettext_context( + html_entity_decode( $term->name ), // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText + "{$taxonomy->label} term name", // phpcs:ignore WordPress.WP.I18n.InterpolatedVariableContext + 'wporg-learn' + ) ); + + if ( $term->description ) { + $term->description = wp_kses_post( translate_with_gettext_context( + html_entity_decode( $term->description ), // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText + "{$taxonomy->label} term description", // phpcs:ignore WordPress.WP.I18n.InterpolatedVariableContext + 'wporg-learn' + ) ); + } + } elseif ( is_string( $term ) ) { + $term = esc_html( translate_with_gettext_context( + html_entity_decode( $term ), // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText + "{$taxonomy->label} term name", // phpcs:ignore WordPress.WP.I18n.InterpolatedVariableContext + 'wporg-learn' + ) ); + } + + return $term; +} + +/** + * Translate a group of terms. + * + * @param WP_Term[] $terms + * + * @return WP_Term[] + */ +function translate_terms( array $terms, array $taxonomies ) { + if ( 'en_US' === get_locale() ) { + return $terms; + } + + // Terms shouldn't be translated in the UI for editing them. + if ( is_admin() ) { + return $terms; + } + + $first_term = reset( $terms ); + if ( ! $first_term instanceof WP_Term && ! is_string( $first_term ) ) { + return $terms; + } + + // If the terms query has multiple taxonomies, we don't know which one a term will belong to. + if ( count( $taxonomies ) > 1 ) { + return $terms; + } + + $taxonomy = reset( $taxonomies ); + + foreach ( $terms as $index => $term ) { + $terms[ $index ] = translate_term( $term, $taxonomy ); + } + + return $terms; +} diff --git a/wp-content/plugins/wporg-learn/wporg-learn.php b/wp-content/plugins/wporg-learn/wporg-learn.php index 3e0a5f9d6..06e40aa36 100644 --- a/wp-content/plugins/wporg-learn/wporg-learn.php +++ b/wp-content/plugins/wporg-learn/wporg-learn.php @@ -64,10 +64,12 @@ function get_views_path() { * @return void */ function load_files() { + require_once get_includes_path() . 'admin.php'; require_once get_includes_path() . 'blocks.php'; require_once get_includes_path() . 'class-markdown-import.php'; require_once get_includes_path() . 'events.php'; require_once get_includes_path() . 'form.php'; + require_once get_includes_path() . 'i18n.php'; require_once get_includes_path() . 'post-meta.php'; require_once get_includes_path() . 'post-type.php'; require_once get_includes_path() . 'sensei.php';