feat(api): proxy Translation Memory over bat.sock

This commit is contained in:
2026-09-06 22:50:59 +08:00
parent 7d6389806b
commit e373e3fd32
9 changed files with 848 additions and 42 deletions
+199 -16
View File
@@ -489,6 +489,7 @@ func TestTranslationWorkerRunSendsProviderConfig(t *testing.T) {
}
if params.Provider != "mock" ||
params.FixturePath != "/tmp/mock-provider.json" ||
params.TranslationMemoryPath != "/tmp/translation-memory.sqlite" ||
params.Concurrency == nil || *params.Concurrency != concurrency ||
params.MaxAttempts == nil || *params.MaxAttempts != maxAttempts ||
params.LeaseSeconds == nil || *params.LeaseSeconds != leaseSeconds ||
@@ -506,14 +507,15 @@ func TestTranslationWorkerRunSendsProviderConfig(t *testing.T) {
"task_id": "task-worker-1",
"kind": "translation.worker.run",
"worker": map[string]any{
"provider": "mock",
"fixture_path": "/tmp/mock-provider.json",
"concurrency": concurrency,
"max_attempts": maxAttempts,
"lease_seconds": leaseSeconds,
"retry_backoff_seconds": retryBackoff,
"max_tasks": maxTasks,
"worker_id": "dashboard-worker",
"provider": "mock",
"fixture_path": "/tmp/mock-provider.json",
"translation_memory_path": "/tmp/translation-memory.sqlite",
"concurrency": concurrency,
"max_attempts": maxAttempts,
"lease_seconds": leaseSeconds,
"retry_backoff_seconds": retryBackoff,
"max_tasks": maxTasks,
"worker_id": "dashboard-worker",
},
},
},
@@ -521,14 +523,15 @@ func TestTranslationWorkerRunSendsProviderConfig(t *testing.T) {
})
result, err := client.TranslationWorkerRun(context.Background(), TranslationWorkerRunParams{
Provider: "mock",
FixturePath: "/tmp/mock-provider.json",
Concurrency: &concurrency,
MaxAttempts: &maxAttempts,
LeaseSeconds: &leaseSeconds,
RetryBackoffSeconds: &retryBackoff,
MaxTasks: &maxTasks,
WorkerID: "dashboard-worker",
Provider: "mock",
FixturePath: "/tmp/mock-provider.json",
TranslationMemoryPath: "/tmp/translation-memory.sqlite",
Concurrency: &concurrency,
MaxAttempts: &maxAttempts,
LeaseSeconds: &leaseSeconds,
RetryBackoffSeconds: &retryBackoff,
MaxTasks: &maxTasks,
WorkerID: "dashboard-worker",
})
if err != nil {
t.Fatalf("TranslationWorkerRun error: %v", err)
@@ -536,12 +539,192 @@ func TestTranslationWorkerRunSendsProviderConfig(t *testing.T) {
if result.TaskID != "task-worker-1" ||
result.Kind != "translation.worker.run" ||
result.Worker.Concurrency != concurrency ||
result.Worker.TranslationMemoryPath != "/tmp/translation-memory.sqlite" ||
result.Worker.RetryBackoffSeconds != retryBackoff ||
result.Worker.MaxTasks == nil || *result.Worker.MaxTasks != maxTasks {
t.Fatalf("unexpected result: %#v", result)
}
}
func TestTranslationMemoryTypedContract(t *testing.T) {
limit := uint64(25)
client := newTestClient(t, func(t *testing.T, req testRequest) testResponse {
switch req.Method {
case "translation.memory.summary":
var params TranslationMemorySummaryParams
if err := json.Unmarshal(req.Params, &params); err != nil {
t.Fatalf("decode summary params: %v", err)
}
if params.TranslationMemoryPath != "/var/lib/bat/translation-memory.sqlite" {
t.Fatalf("summary params=%#v", params)
}
return testResponse{
Result: testEnvelope{
OK: true, Status: "ok", RequestID: "req-tm-summary",
Data: map[string]any{
"available": true,
"path": "/var/lib/bat/translation-memory.sqlite",
"schema_version": 1,
"summary": map[string]any{
"schema_version": 1,
"record_count": 3,
"trusted_count": 1,
"candidate_count": 1,
"superseded_count": 1,
"rejected_count": 0,
},
},
},
}
case "translation.memory.query":
var params TranslationMemoryQueryParams
if err := json.Unmarshal(req.Params, &params); err != nil {
t.Fatalf("decode query params: %v", err)
}
if params.SourceText != "Hello" ||
params.SourceContext["destination"] != "Bundle/dialogue.bundle" ||
params.Limit == nil || *params.Limit != limit {
t.Fatalf("query params=%#v", params)
}
return testResponse{
Result: testEnvelope{
OK: true, Status: "ok", RequestID: "req-tm-query",
Data: map[string]any{
"available": true,
"path": "/var/lib/bat/translation-memory.sqlite",
"source_text": "Hello",
"source_context": map[string]any{
"destination": "Bundle/dialogue.bundle",
"field_path": "Dialog.Message",
},
"matches": []any{
map[string]any{
"entry": map[string]any{
"record_id": "tm-record-1",
"source_text": "Hello",
"source_hash": "hash-source",
"normalized_source_text": "hello",
"source_context": map[string]any{
"destination": "Bundle/dialogue.bundle",
"field_path": "Dialog.Message",
},
"source_context_hash": "hash-context",
"translated_text": "你好",
"translation_source_kind": "provider",
"trust_status": "trusted",
"official_release_id": "release-1",
"source_trace": map[string]any{
"official_release_id": "release-1",
"unit_id": "textunit-1",
"task_id": "task-1",
"destination": "Bundle/dialogue.bundle",
"archive_entry": "dialogue.json",
"serialized_file": "globalgamemanagers",
"path_id": 42,
"class_id": 114,
"field_path": "Dialog.Message",
"format": "json",
"asset_name": "Dialogue",
"text_source_kind": "text_asset",
},
"provider": "mock",
"provider_run_id": "provider-run-1",
"created_unix_seconds": 100,
"updated_unix_seconds": 200,
"trusted_unix_seconds": 200,
"trusted_by": "reviewer",
"trusted_reason": "reviewed",
},
"match_kind": "strong_exact",
"can_auto_reuse": true,
},
},
},
},
}
case "translation.memory.confirm":
var params TranslationMemoryConfirmParams
if err := json.Unmarshal(req.Params, &params); err != nil {
t.Fatalf("decode confirm params: %v", err)
}
if params.RecordID != "tm-record-1" ||
params.Reviewer != "reviewer" ||
params.Reason != "reviewed" ||
params.TranslationMemoryPath != "/var/lib/bat/translation-memory.sqlite" {
t.Fatalf("confirm params=%#v", params)
}
return testResponse{
Result: testEnvelope{
OK: true, Status: "ok", RequestID: "req-tm-confirm",
Data: map[string]any{
"available": true,
"path": "/var/lib/bat/translation-memory.sqlite",
"entry": map[string]any{
"record_id": "tm-record-1",
"translation_source_kind": "provider",
"trust_status": "trusted",
},
},
},
}
default:
t.Fatalf("unexpected method %q", req.Method)
return testResponse{}
}
})
summary, err := client.TranslationMemorySummary(context.Background(), TranslationMemorySummaryParams{
TranslationMemoryPath: "/var/lib/bat/translation-memory.sqlite",
})
if err != nil {
t.Fatalf("TranslationMemorySummary error: %v", err)
}
if !summary.Available || summary.Summary == nil ||
summary.Summary.TrustedCount != 1 || summary.Summary.CandidateCount != 1 {
t.Fatalf("summary=%#v", summary)
}
query, err := client.TranslationMemoryQuery(context.Background(), TranslationMemoryQueryParams{
TranslationMemoryPath: "/var/lib/bat/translation-memory.sqlite",
SourceText: "Hello",
SourceContext: TranslationMemoryContext{
"destination": "Bundle/dialogue.bundle",
"field_path": "Dialog.Message",
},
Limit: &limit,
})
if err != nil {
t.Fatalf("TranslationMemoryQuery error: %v", err)
}
if len(query.Matches) != 1 ||
query.Matches[0].MatchKind != "strong_exact" ||
!query.Matches[0].CanAutoReuse ||
query.Matches[0].Entry.TrustStatus != "trusted" ||
query.Matches[0].Entry.TranslationSourceKind != "provider" ||
query.Matches[0].Entry.SourceTrace.UnitID == nil ||
*query.Matches[0].Entry.SourceTrace.UnitID != "textunit-1" ||
query.Matches[0].Entry.SourceTrace.PathID == nil ||
*query.Matches[0].Entry.SourceTrace.PathID != 42 ||
query.Matches[0].Entry.TrustedBy == nil ||
*query.Matches[0].Entry.TrustedBy != "reviewer" {
t.Fatalf("query=%#v", query)
}
confirmed, err := client.TranslationMemoryConfirm(context.Background(), TranslationMemoryConfirmParams{
TranslationMemoryPath: "/var/lib/bat/translation-memory.sqlite",
RecordID: "tm-record-1",
Reviewer: "reviewer",
Reason: "reviewed",
})
if err != nil {
t.Fatalf("TranslationMemoryConfirm error: %v", err)
}
if !confirmed.Available || confirmed.Entry.TrustStatus != "trusted" ||
confirmed.Entry.RecordID != "tm-record-1" {
t.Fatalf("confirmed=%#v", confirmed)
}
}
func TestTranslationProofreadUsesRustMethod(t *testing.T) {
client := newTestClient(t, func(t *testing.T, req testRequest) testResponse {
if req.Method != "translation.proofread" {