Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions features/updatepo.feature
Original file line number Diff line number Diff line change
Expand Up @@ -465,3 +465,145 @@ Feature: Update existing PO files from a POT file
"""
"X-Domain: foo-plugin\n"
"""

Scenario: Preserves obsolete translations and file-level comments with --no-purge
Given an empty foo-plugin directory
And a foo-plugin/foo-plugin.pot file:
"""
# Copyright (C) 2018 Foo Plugin
# This file is distributed under the same license as the Foo Plugin package.
msgid ""
msgstr ""
"Project-Id-Version: Foo Plugin\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/foo-plugin\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2018-05-02T22:06:24+00:00\n"
"PO-Revision-Date: 2018-05-02T22:06:24+00:00\n"
"X-Domain: foo-plugin\n"

#: foo-plugin.php:1
msgid "Some string"
msgstr ""
"""
And a foo-plugin/foo-plugin-de_DE.po file:
"""
# Copyright (C) 2018 Foo Plugin
# This file is distributed under the same license as the Foo Plugin package.
msgid ""
msgstr ""
"Project-Id-Version: Foo Plugin\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/foo-plugin\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2018-05-02T22:06:24+00:00\n"
"PO-Revision-Date: 2018-05-02T22:06:24+00:00\n"
"X-Domain: foo-plugin\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#: foo-plugin.php:1
msgid "Some string"
msgstr "Some translated string"

#~ msgid "Obsolete string"
#~ msgstr "Veralteter String"
"""

When I run `wp i18n update-po foo-plugin/foo-plugin.pot foo-plugin/foo-plugin-de_DE.po --no-purge`
Then STDOUT should be:
"""
Success: Updated 1 file.
"""
And STDERR should be empty
And the foo-plugin/foo-plugin-de_DE.po file should contain:
"""
# Copyright (C) 2018 Foo Plugin
# This file is distributed under the same license as the Foo Plugin package.
"""
And the foo-plugin/foo-plugin-de_DE.po file should contain:
"""
#~ msgid "Obsolete string"
#~ msgstr "Veralteter String"
"""
And the foo-plugin/foo-plugin-de_DE.po file should contain:
"""
#: foo-plugin.php:1
msgid "Some string"
msgstr "Some translated string"
"""

Scenario: Removes obsolete translations and comments by default
Given an empty foo-plugin directory
And a foo-plugin/foo-plugin.pot file:
"""
msgid ""
msgstr ""
"Project-Id-Version: Foo Plugin\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/foo-plugin\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2018-05-02T22:06:24+00:00\n"
"PO-Revision-Date: 2018-05-02T22:06:24+00:00\n"
"X-Domain: foo-plugin\n"

#: foo-plugin.php:1
msgid "Some string"
msgstr ""
"""
And a foo-plugin/foo-plugin-de_DE.po file:
"""
# Copyright (C) 2018 Foo Plugin
# This file is distributed under the same license as the Foo Plugin package.
msgid ""
msgstr ""
"Project-Id-Version: Foo Plugin\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/foo-plugin\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2018-05-02T22:06:24+00:00\n"
"PO-Revision-Date: 2018-05-02T22:06:24+00:00\n"
"X-Domain: foo-plugin\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#: foo-plugin.php:1
msgid "Some string"
msgstr "Some translated string"

#~ msgid "Obsolete string"
#~ msgstr "Veralteter String"
"""

When I run `wp i18n update-po foo-plugin/foo-plugin.pot foo-plugin/foo-plugin-de_DE.po`
Then STDOUT should be:
"""
Success: Updated 1 file.
"""
And STDERR should be empty
And the foo-plugin/foo-plugin-de_DE.po file should not contain:
"""
# Copyright (C) 2018 Foo Plugin
"""
And the foo-plugin/foo-plugin-de_DE.po file should not contain:
"""
#~ msgid "Obsolete string"
"""
And the foo-plugin/foo-plugin-de_DE.po file should contain:
"""
#: foo-plugin.php:1
msgid "Some string"
msgstr "Some translated string"
"""
100 changes: 99 additions & 1 deletion src/UpdatePoCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ class UpdatePoCommand extends WP_CLI_Command {
* : PO file to update or a directory containing multiple PO files.
* Defaults to all PO files in the source directory.
*
* [--purge]
* : Remove obsolete strings and replace translator comments. Defaults to true.
* By default, strings not found in the POT file are removed, and translator comments are replaced with those from the POT file.
* Use `--no-purge` to preserve obsolete translations (marked with #~) and existing translator comments like copyright notices.
*
* ## EXAMPLES
*
* # Update all PO files from a POT file in the current directory.
Expand All @@ -41,6 +46,10 @@ class UpdatePoCommand extends WP_CLI_Command {
* $ wp i18n update-po example-plugin.pot languages
* Success: Updated 2 files.
*
* # Update PO files while keeping obsolete strings and translator comments.
* $ wp i18n update-po example-plugin.pot --no-purge
* Success: Updated 3 files.
*
* @when before_wp_load
*
* @throws WP_CLI\ExitException
Expand Down Expand Up @@ -69,6 +78,16 @@ public function __invoke( $args, $assoc_args ) {

$pot_translations = Translations::fromPoFile( $source );

// Build merge flags based on options
$merge_flags = Merge::ADD | Merge::EXTRACTED_COMMENTS_THEIRS | Merge::REFERENCES_THEIRS | Merge::DOMAIN_OVERRIDE;

$purge = Utils\get_flag_value( $assoc_args, 'purge', true );

if ( $purge ) {
// By default, remove obsolete entries and replace translator comments
$merge_flags |= Merge::REMOVE | Merge::COMMENTS_THEIRS;
}

$result_count = 0;
/** @var DirectoryIterator $file */
foreach ( $files as $file ) {
Expand All @@ -81,20 +100,99 @@ public function __invoke( $args, $assoc_args ) {
continue;
}

// Preserve file-level comments when --no-purge is set
$file_comments = '';
if ( ! $purge ) {
$file_comments = $this->extract_file_comments( $file->getPathname() );
}

$po_translations = Translations::fromPoFile( $file->getPathname() );
$po_translations->mergeWith(
$pot_translations,
Merge::ADD | Merge::REMOVE | Merge::COMMENTS_THEIRS | Merge::EXTRACTED_COMMENTS_THEIRS | Merge::REFERENCES_THEIRS | Merge::DOMAIN_OVERRIDE
$merge_flags
);

if ( ! $po_translations->toPoFile( $file->getPathname() ) ) {
WP_CLI::warning( sprintf( 'Could not update file %s', $file->getPathname() ) );
continue;
}

// Restore file-level comments when --no-purge is set
if ( ! $purge && ! empty( $file_comments ) ) {
$this->restore_file_comments( $file->getPathname(), $file_comments );
}

++$result_count;
}

WP_CLI::success( sprintf( 'Updated %d %s.', $result_count, Utils\pluralize( 'file', $result_count ) ) );
}

/**
* Extract file-level comments from a PO file.
*
* These are comments that appear before the first msgid in the file.
*
* @param string $file_path Path to the PO file.
* @return string The file-level comments.
*/
private function extract_file_comments( $file_path ) {
$content = file_get_contents( $file_path );
if ( false === $content ) {
return '';
}

$lines = explode( "\n", $content );
$file_comments = [];
$found_msgid = false;

foreach ( $lines as $line ) {
$trimmed = trim( $line );

// Stop when we hit the first msgid
if ( preg_match( '/^msgid\s/', $trimmed ) ) {
$found_msgid = true;
break;
}

// Collect comment lines
if ( preg_match( '/^#([^.,:~]|$)/', $trimmed ) ) {
$file_comments[] = $line;
}
}

return $found_msgid && ! empty( $file_comments ) ? implode( "\n", $file_comments ) . "\n" : '';
}

/**
* Restore file-level comments to a PO file.
*
* @param string $file_path Path to the PO file.
* @param string $comments The file-level comments to restore.
* @return bool True on success, false on failure.
*/
private function restore_file_comments( $file_path, $comments ) {
$content = file_get_contents( $file_path );
if ( false === $content ) {
return false;
}

// Prepend the comments to the file content
$updated_content = $comments . $content;

// Use atomic file operation with temporary file
$temp_file = $file_path . '.tmp';
if ( false === file_put_contents( $temp_file, $updated_content ) ) {
return false;
}

// Rename is atomic on most filesystems
if ( ! rename( $temp_file, $file_path ) ) {
// Clean up temp file on failure
@unlink( $temp_file );
return false;
}

return true;
}
}
Loading