This commit is contained in:
hanyixuanten
2026-07-25 23:45:39 +08:00
parent 77ce36cdfe
commit 811003bc5c
10 changed files with 1960 additions and 3 deletions
+67
View File
@@ -0,0 +1,67 @@
name: Package Plugin
on:
push:
branches:
- master
tags:
- 'v*'
pull_request:
branches:
- master
workflow_dispatch:
permissions:
contents: read
jobs:
test-and-package:
name: Test and package plugin
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
tools: composer:v2
coverage: none
- name: Install Composer dependencies
run: composer install --no-interaction --prefer-dist --no-progress
- name: Run tests
run: composer test
- name: Lint PHP
run: composer lint
- name: Build installable plugin archive
id: package
run: |
set -eu
plugin_slug="wp-translate"
version="$(grep -E '^ \* Version:' wp-translate.php | sed -E 's/^ \* Version: *//')"
archive_name="${plugin_slug}-${version}.zip"
rm -rf build
mkdir -p "build/${plugin_slug}"
cp wp-translate.php README.md "build/${plugin_slug}/"
cp -R includes "build/${plugin_slug}/"
(
cd build
zip -qr "../${archive_name}" "${plugin_slug}"
)
printf 'archive=%s\n' "${archive_name}" >> "$GITHUB_OUTPUT"
- name: Upload plugin archive
uses: actions/upload-artifact@v4
with:
name: wp-translate-plugin
path: ${{ steps.package.outputs.archive }}
if-no-files-found: error
retention-days: 30
+7
View File
@@ -1,3 +1,10 @@
/vendor/
/node_modules/
/.phpunit.result.cache
/coverage/
/build/
/dist/
/phpunit.xml
/.intelephense/
.DS_Store
*.zip
+1 -1
View File
@@ -45,5 +45,5 @@ Unless a user explicitly requests a refresh, each translatable value must be tra
- Mock Baidu API calls in every test. Tests must not require live credentials or outbound network access.
- Keep changes narrow and avoid unrelated refactors or formatting churn.
- Plugin slug and PHP prefix: `wp-translate` and `wpt_`/`WPT_`. The current minimum supported PHP version is 8.1 and the current minimum WordPress version is 6.4.
- Composer is not installed in the initial development environment. Until a WordPress test suite is added, validate syntax with `find . -path './.git' -prune -o -type f -name '*.php' -print0 | xargs -0 -n1 php -l`.
- Composer manages the development dependencies. Run `composer test` for isolated unit tests and `composer lint` for PHP syntax checks. WordPress API calls must be mocked in tests; tests must not require live credentials, external network access, or a WordPress database.
- Document supported languages, routing modes, data retention, translation lifecycle, and known limitations in the README whenever the implementation changes.
+9 -1
View File
@@ -47,7 +47,15 @@ Use HTTPS and restrict database access appropriately because WordPress options a
## Development
The project currently has no Composer dependencies. Validate PHP syntax with:
Install development dependencies with `composer install`. The project includes WordPress API stubs for Intelephense and PHPUnit tests that use fake providers and storage, so they do not need WordPress, a database, credentials, or network access.
Run tests with:
```sh
composer test
```
Validate PHP syntax with:
```sh
find . -path './.git' -prune -o -type f -name '*.php' -print0 | xargs -0 -n1 php -l
+17
View File
@@ -0,0 +1,17 @@
{
"name": "wp-translate/wp-translate",
"description": "Persistent multilingual WordPress translations powered by Baidu Translate.",
"type": "wordpress-plugin",
"license": "proprietary",
"require": {
"php": "^8.1"
},
"require-dev": {
"php-stubs/wordpress-stubs": "^6.8",
"phpunit/phpunit": "^12"
},
"scripts": {
"lint": "find . -path './.git' -prune -o -path './vendor' -prune -o -type f -name '*.php' -print0 | xargs -0 -n1 php -l",
"test": "phpunit"
}
}
Generated
+1754
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="WP Translate">
<directory suffix="Test.php">tests</directory>
</testsuite>
</testsuites>
</phpunit>
+19
View File
@@ -0,0 +1,19 @@
<?php
use PHPUnit\Framework\TestCase;
class TranslationIdentityTest extends TestCase {
public function test_key_is_stable_for_identical_translation_input() {
$first = WPT_Translation_Identity::key( 'Hello', 'en', 'zh', 'post:15:post_title', 'provider-v1' );
$second = WPT_Translation_Identity::key( 'Hello', 'en', 'zh', 'post:15:post_title', 'provider-v1' );
$this->assertSame( $first, $second );
}
public function test_key_changes_when_source_value_changes() {
$first = WPT_Translation_Identity::key( 'Hello', 'en', 'zh', 'post:15:post_title', 'provider-v1' );
$second = WPT_Translation_Identity::key( 'Hello again', 'en', 'zh', 'post:15:post_title', 'provider-v1' );
$this->assertNotSame( $first, $second );
}
}
+68
View File
@@ -0,0 +1,68 @@
<?php
use PHPUnit\Framework\TestCase;
class WPT_Test_Translation_Store extends WPT_Translation_Store {
public $values = array();
public $save_calls = array();
public function find_valid( $identity_key ) {
return $this->values[ $identity_key ] ?? null;
}
public function save( $identity_key, $source_language, $target_language, $field_context, $source_fingerprint, $translated_value, $status ) {
$this->save_calls[] = compact( 'identity_key', 'source_language', 'target_language', 'field_context', 'source_fingerprint', 'translated_value', 'status' );
if ( 'complete' === $status ) {
$this->values[ $identity_key ] = array( 'translated_value' => $translated_value );
}
}
}
class WPT_Test_Translation_Provider implements WPT_Translation_Provider {
public $calls = 0;
public function get_version() {
return 'test-provider-v1';
}
public function translate( $source_value, $source_language, $target_language, $context ) {
++$this->calls;
return WPT_Translation_Result::success( '[' . $target_language . '] ' . $source_value );
}
}
class TranslationServiceTest extends TestCase {
public function test_second_request_reuses_persisted_translation_without_provider_call() {
$store = new WPT_Test_Translation_Store();
$provider = new WPT_Test_Translation_Provider();
$service = new WPT_Translation_Service( $store, $provider );
$first = $service->get_or_translate( 'Welcome', 'en', 'zh', 'post:8:post_title' );
$second = $service->get_or_translate( 'Welcome', 'en', 'zh', 'post:8:post_title' );
$this->assertTrue( $first->success );
$this->assertSame( '[zh] Welcome', $second->value );
$this->assertSame( 1, $provider->calls );
$this->assertCount( 1, $store->save_calls );
}
public function test_failed_request_is_persisted_with_failed_status() {
$store = new WPT_Test_Translation_Store();
$provider = new class implements WPT_Translation_Provider {
public function get_version() {
return 'test-provider-v1';
}
public function translate( $source_value, $source_language, $target_language, $context ) {
return WPT_Translation_Result::failure( 'request_failed', 'Network error' );
}
};
$service = new WPT_Translation_Service( $store, $provider );
$result = $service->get_or_translate( 'Welcome', 'en', 'zh', 'post:8:post_title' );
$this->assertFalse( $result->success );
$this->assertSame( 'failed', $store->save_calls[0]['status'] );
}
}
+9
View File
@@ -0,0 +1,9 @@
<?php
define( 'ABSPATH', __DIR__ . '/' );
require_once dirname( __DIR__ ) . '/includes/interface-wpt-translation-provider.php';
require_once dirname( __DIR__ ) . '/includes/class-wpt-translation-result.php';
require_once dirname( __DIR__ ) . '/includes/class-wpt-translation-identity.php';
require_once dirname( __DIR__ ) . '/includes/class-wpt-translation-store.php';
require_once dirname( __DIR__ ) . '/includes/class-wpt-translation-service.php';