feat(patch):统一清单驱动汉化发布
bat-rust / Build and test Rust (push) Canceled after 0s
bat-rust / Build and test Go API (push) Canceled after 0s

This commit is contained in:
2026-09-12 01:35:29 +08:00
parent 69b6e36bf0
commit 8a77502272
26 changed files with 2250 additions and 158 deletions
+13 -2
View File
@@ -1306,8 +1306,19 @@ func validateTranslationMemoryConfirmParams(params backendrpc.TranslationMemoryC
func validateLocalizedPublishParams(params backendrpc.LocalizedPublishParams) error {
hasFile := strings.TrimSpace(params.TranslationFile) != ""
if hasFile == params.FromWorker {
return errors.New("localized publish requires exactly one of translation_file or from_worker")
hasManifest := strings.TrimSpace(params.PatchManifest) != ""
inputs := 0
if hasFile {
inputs++
}
if params.FromWorker {
inputs++
}
if hasManifest {
inputs++
}
if inputs != 1 {
return errors.New("localized publish requires exactly one of translation_file, from_worker, or patch_manifest")
}
return nil
}
+9
View File
@@ -1954,6 +1954,7 @@ func TestAdminControlForwardsAllowlistedActions(t *testing.T) {
{name: "translation glossary deprecate", action: "translation-glossary-deprecate", body: `{"term_id":"term-sensei","reviewer":"reviewer","reason":"retired"}`, rpcMethod: "translation.glossary.deprecate", call: "translation.glossary.deprecate"},
{name: "translation glossary delete", action: "translation-glossary-delete", body: `{"term_id":"term-sensei","reviewer":"reviewer","reason":"duplicate"}`, rpcMethod: "translation.glossary.delete", call: "translation.glossary.delete"},
{name: "localized publish", action: "localized-publish", body: `{"from_worker":true,"localized_release_id":"localized-1","force":true}`, rpcMethod: "localized.publish", call: "localized.publish"},
{name: "localized patch manifest publish", action: "localized-publish", body: `{"patch_manifest":"/tmp/patch-manifest.json","localized_release_id":"localized-1"}`, rpcMethod: "localized.publish", call: "localized.publish"},
{name: "localized rollback", action: "localized-rollback", body: `{"localized_release_id":"localized-1"}`, rpcMethod: "localized.rollback", call: "localized.rollback"},
}
for _, tc := range tests {
@@ -2041,6 +2042,14 @@ func TestAdminControlForwardsAllowlistedActions(t *testing.T) {
if recorder.Code != http.StatusBadRequest {
t.Fatalf("invalid localized publish status=%d body=%s", recorder.Code, recorder.Body.String())
}
request = httptest.NewRequest(http.MethodPost, "/admin/control/localized-publish", strings.NewReader(`{"from_worker":true,"patch_manifest":"/tmp/patch-manifest.json"}`))
request.Header.Set("Authorization", "Bearer control-token")
recorder = httptest.NewRecorder()
s.Handler().ServeHTTP(recorder, request)
if recorder.Code != http.StatusBadRequest {
t.Fatalf("invalid localized patch manifest status=%d body=%s", recorder.Code, recorder.Body.String())
}
}
func TestAdminLocalizedStatusRequiresAuthAndForwards(t *testing.T) {
+2
View File
@@ -730,6 +730,8 @@ paths:
type: string
from_worker:
type: boolean
patch_manifest:
type: string
localized_release_id:
type: string
responses:
+3 -1
View File
@@ -637,10 +637,12 @@ type GlossaryDiagnoseReport struct {
}
// LocalizedPublishParams selects the source of one localized release
// publication. TranslationFile and FromWorker are mutually exclusive.
// publication. Exactly one of TranslationFile, FromWorker, or PatchManifest
// must be set.
type LocalizedPublishParams struct {
TranslationFile string `json:"translation_file,omitempty"`
FromWorker bool `json:"from_worker,omitempty"`
PatchManifest string `json:"patch_manifest,omitempty"`
LocalizedReleaseID string `json:"localized_release_id,omitempty"`
Force bool `json:"force,omitempty"`
}
+32
View File
@@ -797,6 +797,38 @@ func TestLocalizedPublishSendsWorkerSourceAndReleaseOptions(t *testing.T) {
}
}
func TestLocalizedPublishSendsPatchManifestSource(t *testing.T) {
client := newTestClient(t, func(t *testing.T, req testRequest) testResponse {
var params LocalizedPublishParams
if err := json.Unmarshal(req.Params, &params); err != nil {
t.Fatalf("decode params: %v", err)
}
if params.PatchManifest != "/tmp/patch-manifest.json" ||
params.TranslationFile != "" || params.FromWorker {
t.Fatalf("params = %#v", params)
}
return testResponse{
Result: testEnvelope{
OK: true,
Status: "ok",
RequestID: "req-test-localized-patch-manifest",
Data: map[string]any{"status": "published"},
},
}
})
raw, err := client.LocalizedPublish(context.Background(), LocalizedPublishParams{
PatchManifest: "/tmp/patch-manifest.json",
LocalizedReleaseID: "localized-v1",
})
if err != nil {
t.Fatalf("LocalizedPublish error: %v", err)
}
if !json.Valid(raw) {
t.Fatalf("invalid raw JSON: %s", string(raw))
}
}
func TestLocalizedRollbackSendsExpectedRelease(t *testing.T) {
client := newTestClient(t, func(t *testing.T, req testRequest) testResponse {
if req.Method != "localized.rollback" {