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
+134
View File
@@ -54,6 +54,8 @@ func (s *Server) handleAdminIndex(w http.ResponseWriter, r *http.Request) {
"/admin/parse/errors",
"/admin/translation/tasks",
"/admin/translation/handoff",
"/admin/translation/memory/summary",
"/admin/translation/memory/query",
"/admin/translation/status",
},
Controls: []string{
@@ -72,6 +74,7 @@ func (s *Server) handleAdminIndex(w http.ResponseWriter, r *http.Request) {
"/admin/control/translation-task-update",
"/admin/control/translation-worker-run",
"/admin/control/translation-proofread",
"/admin/control/translation-memory-confirm",
"/admin/control/localized-publish",
"/admin/control/localized-rollback",
},
@@ -117,6 +120,10 @@ func (s *Server) handleAdminControl(w http.ResponseWriter, r *http.Request) {
s.handleAdminTranslationProofread(w, r)
return
}
if action == "translation-memory-confirm" {
s.handleAdminTranslationMemoryConfirm(w, r)
return
}
if action == "localized-publish" {
s.handleAdminLocalizedPublish(w, r)
return
@@ -274,6 +281,34 @@ func (s *Server) handleAdminTranslationProofread(w http.ResponseWriter, r *http.
})
}
func (s *Server) handleAdminTranslationMemoryConfirm(w http.ResponseWriter, r *http.Request) {
backend, ok := s.backend.(TranslationMemoryBackend)
if !ok || backend == nil {
writeErrorJSON(w, http.StatusServiceUnavailable, "translation_memory_backend_unavailable", "Rust bat Translation Memory backend is unavailable")
return
}
var params backendrpc.TranslationMemoryConfirmParams
if !decodeAdminTranslationJSON(w, r, &params) {
return
}
if err := validateTranslationMemoryConfirmParams(params); err != nil {
writeErrorJSON(w, http.StatusBadRequest, "invalid_translation_memory_params", err.Error())
return
}
result, err := backend.TranslationMemoryConfirm(r.Context(), params)
if err != nil {
s.writeControlBackendError(w, "translation-memory-confirm", err)
return
}
writeNoStoreJSON(w, http.StatusAccepted, AdminControlResponse{
Service: "bat-api",
Action: "translation-memory-confirm",
RPCMethod: "translation.memory.confirm",
Status: "accepted",
Result: result,
})
}
func (s *Server) handleAdminLocalizedPublish(w http.ResponseWriter, r *http.Request) {
backend, ok := s.backend.(LocalizedBackend)
if !ok || backend == nil {
@@ -656,6 +691,66 @@ func (s *Server) handleAdminTranslationHandoff(w http.ResponseWriter, r *http.Re
writeNoStoreJSON(w, http.StatusOK, result)
}
func (s *Server) handleAdminTranslationMemorySummary(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet && r.Method != http.MethodHead {
writeErrorJSON(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
return
}
if !s.requireAdminToken(w, r) {
return
}
backend, ok := s.backend.(TranslationMemoryBackend)
if !ok || backend == nil {
writeErrorJSON(w, http.StatusServiceUnavailable, "translation_memory_backend_unavailable", "Rust bat Translation Memory backend is unavailable")
return
}
params := backendrpc.TranslationMemorySummaryParams{
TranslationMemoryPath: firstTrimmedQuery(r.URL.Query(), "translation_memory_path", "tm_path"),
}
result, err := backend.TranslationMemorySummary(r.Context(), params)
if err != nil {
s.writeControlBackendError(w, "translation-memory-summary", err)
return
}
if r.Method == http.MethodHead {
w.Header().Set("Cache-Control", "no-store")
w.WriteHeader(http.StatusOK)
return
}
writeNoStoreJSON(w, http.StatusOK, result)
}
func (s *Server) handleAdminTranslationMemoryQuery(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet && r.Method != http.MethodHead {
writeErrorJSON(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
return
}
if !s.requireAdminToken(w, r) {
return
}
backend, ok := s.backend.(TranslationMemoryBackend)
if !ok || backend == nil {
writeErrorJSON(w, http.StatusServiceUnavailable, "translation_memory_backend_unavailable", "Rust bat Translation Memory backend is unavailable")
return
}
params, err := translationMemoryQueryParams(r)
if err != nil {
writeErrorJSON(w, http.StatusBadRequest, "invalid_translation_memory_query", err.Error())
return
}
result, err := backend.TranslationMemoryQuery(r.Context(), params)
if err != nil {
s.writeControlBackendError(w, "translation-memory-query", err)
return
}
if r.Method == http.MethodHead {
w.Header().Set("Cache-Control", "no-store")
w.WriteHeader(http.StatusOK)
return
}
writeNoStoreJSON(w, http.StatusOK, result)
}
func (s *Server) handleAdminSchedules(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet && r.Method != http.MethodHead {
writeErrorJSON(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
@@ -747,6 +842,38 @@ func translationTaskListParams(r *http.Request) (backendrpc.TranslationTaskListP
return params, nil
}
func translationMemoryQueryParams(r *http.Request) (backendrpc.TranslationMemoryQueryParams, error) {
query := r.URL.Query()
sourceText := strings.TrimSpace(query.Get("source_text"))
if sourceText == "" {
return backendrpc.TranslationMemoryQueryParams{}, errors.New("source_text is required")
}
params := backendrpc.TranslationMemoryQueryParams{
TranslationMemoryPath: firstTrimmedQuery(query, "translation_memory_path", "tm_path"),
SourceText: sourceText,
}
if raw := firstTrimmedQuery(query, "source_context", "context"); raw != "" {
var context backendrpc.TranslationMemoryContext
if raw != "null" {
if err := json.Unmarshal([]byte(raw), &context); err != nil {
return backendrpc.TranslationMemoryQueryParams{}, errors.New("source_context must be a JSON object with string values")
}
if context == nil {
return backendrpc.TranslationMemoryQueryParams{}, errors.New("source_context must be a JSON object")
}
params.SourceContext = context
}
}
if raw := strings.TrimSpace(query.Get("limit")); raw != "" {
limit, err := strconv.ParseUint(raw, 10, 64)
if err != nil || limit == 0 || limit > 1000 {
return backendrpc.TranslationMemoryQueryParams{}, errors.New("limit must be in 1..=1000")
}
params.Limit = &limit
}
return params, nil
}
func parseTextUnitQueryParams(r *http.Request) (backendrpc.TextUnitQueryParams, error) {
query := r.URL.Query()
params := backendrpc.TextUnitQueryParams{
@@ -938,6 +1065,13 @@ func validateTranslationWorkerRunParams(params backendrpc.TranslationWorkerRunPa
return nil
}
func validateTranslationMemoryConfirmParams(params backendrpc.TranslationMemoryConfirmParams) error {
if strings.TrimSpace(params.RecordID) == "" || strings.TrimSpace(params.Reviewer) == "" {
return errors.New("Translation Memory confirm requires record_id and reviewer")
}
return nil
}
func validateLocalizedPublishParams(params backendrpc.LocalizedPublishParams) error {
hasFile := strings.TrimSpace(params.TranslationFile) != ""
if hasFile == params.FromWorker {
+110 -8
View File
@@ -629,13 +629,16 @@ func (f *fakeBackend) TaskCancel(ctx context.Context, taskID string) (*backendrp
type controlBackend struct {
*fakeBackend
calls []string
parseTextUnitQueries []backendrpc.TextUnitQueryParams
parseErrorQueries []backendrpc.TextUnitQueryParams
translationTaskUpdates []backendrpc.TranslationTaskUpdateParams
translationTaskListParams []backendrpc.TranslationTaskListParams
localizedPublishParams []backendrpc.LocalizedPublishParams
localizedRollbackParams []backendrpc.LocalizedRollbackParams
calls []string
parseTextUnitQueries []backendrpc.TextUnitQueryParams
parseErrorQueries []backendrpc.TextUnitQueryParams
translationTaskUpdates []backendrpc.TranslationTaskUpdateParams
translationTaskListParams []backendrpc.TranslationTaskListParams
translationMemorySummaryParams []backendrpc.TranslationMemorySummaryParams
translationMemoryQueryParams []backendrpc.TranslationMemoryQueryParams
translationMemoryConfirmParams []backendrpc.TranslationMemoryConfirmParams
localizedPublishParams []backendrpc.LocalizedPublishParams
localizedRollbackParams []backendrpc.LocalizedRollbackParams
}
func (b *controlBackend) DaemonReload(ctx context.Context) (*backendrpc.Ack, error) {
@@ -777,6 +780,51 @@ func (b *controlBackend) TranslationProofread(ctx context.Context) (json.RawMess
return json.RawMessage(`{"translation_workflow_status":"manual_proofreading"}`), nil
}
func (b *controlBackend) TranslationMemorySummary(ctx context.Context, params backendrpc.TranslationMemorySummaryParams) (*backendrpc.TranslationMemorySummaryReport, error) {
b.calls = append(b.calls, "translation.memory.summary")
b.translationMemorySummaryParams = append(b.translationMemorySummaryParams, params)
schemaVersion := uint64(1)
return &backendrpc.TranslationMemorySummaryReport{
Available: true,
Path: params.TranslationMemoryPath,
SchemaVersion: &schemaVersion,
Summary: &backendrpc.TranslationMemorySummary{
SchemaVersion: schemaVersion,
RecordCount: 2,
TrustedCount: 1,
CandidateCount: 1,
SupersededCount: 0,
RejectedCount: 0,
},
}, nil
}
func (b *controlBackend) TranslationMemoryQuery(ctx context.Context, params backendrpc.TranslationMemoryQueryParams) (*backendrpc.TranslationMemoryQueryReport, error) {
b.calls = append(b.calls, "translation.memory.query")
b.translationMemoryQueryParams = append(b.translationMemoryQueryParams, params)
return &backendrpc.TranslationMemoryQueryReport{
Available: true,
Path: params.TranslationMemoryPath,
SourceText: params.SourceText,
SourceContext: params.SourceContext,
Matches: []backendrpc.TranslationMemoryMatch{},
}, nil
}
func (b *controlBackend) TranslationMemoryConfirm(ctx context.Context, params backendrpc.TranslationMemoryConfirmParams) (*backendrpc.TranslationMemoryConfirmReport, error) {
b.calls = append(b.calls, "translation.memory.confirm")
b.translationMemoryConfirmParams = append(b.translationMemoryConfirmParams, params)
return &backendrpc.TranslationMemoryConfirmReport{
Available: true,
Path: params.TranslationMemoryPath,
Entry: backendrpc.TranslationMemoryEntry{
RecordID: params.RecordID,
TranslationSourceKind: "provider",
TrustStatus: "trusted",
},
}, nil
}
func (b *controlBackend) LocalizedStatus(ctx context.Context) (json.RawMessage, error) {
b.calls = append(b.calls, "localized.status")
return json.RawMessage(`{"localized_release_status":"localized","status_code":"localized.published"}`), nil
@@ -960,6 +1008,42 @@ func TestAdminTranslationQueryEndpointsProxyAuthenticatedRequests(t *testing.T)
t.Fatalf("calls=%v", backend.calls)
}
request = httptest.NewRequest(http.MethodGet, "/admin/translation/memory/summary?translation_memory_path=%2Fvar%2Flib%2Fbat%2Ftranslation-memory.sqlite", nil)
request.Header.Set("Authorization", "Bearer translation-token")
recorder = httptest.NewRecorder()
s.Handler().ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("TM summary status=%d body=%s", recorder.Code, recorder.Body.String())
}
if !strings.Contains(recorder.Body.String(), `"trusted_count":1`) ||
len(backend.translationMemorySummaryParams) != 1 ||
backend.translationMemorySummaryParams[0].TranslationMemoryPath != "/var/lib/bat/translation-memory.sqlite" {
t.Fatalf("TM summary body=%s params=%#v", recorder.Body.String(), backend.translationMemorySummaryParams)
}
request = httptest.NewRequest(http.MethodGet, "/admin/translation/memory/query?source_text=Hello&source_context=%7B%22destination%22%3A%22Bundle%2Fdialogue.bundle%22%7D&limit=25", nil)
request.Header.Set("Authorization", "Bearer translation-token")
recorder = httptest.NewRecorder()
s.Handler().ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("TM query status=%d body=%s", recorder.Code, recorder.Body.String())
}
if len(backend.translationMemoryQueryParams) != 1 ||
backend.translationMemoryQueryParams[0].SourceText != "Hello" ||
backend.translationMemoryQueryParams[0].SourceContext["destination"] != "Bundle/dialogue.bundle" ||
backend.translationMemoryQueryParams[0].Limit == nil ||
*backend.translationMemoryQueryParams[0].Limit != 25 {
t.Fatalf("TM query body=%s params=%#v", recorder.Body.String(), backend.translationMemoryQueryParams)
}
request = httptest.NewRequest(http.MethodGet, "/admin/translation/memory/query", nil)
request.Header.Set("Authorization", "Bearer translation-token")
recorder = httptest.NewRecorder()
s.Handler().ServeHTTP(recorder, request)
if recorder.Code != http.StatusBadRequest {
t.Fatalf("missing TM query source status=%d body=%s", recorder.Code, recorder.Body.String())
}
request = httptest.NewRequest(http.MethodGet, "/admin/translation/tasks?limit=0", nil)
request.Header.Set("Authorization", "Bearer translation-token")
recorder = httptest.NewRecorder()
@@ -1502,7 +1586,9 @@ func TestOpenAPIAndAdminReservedEndpoints(t *testing.T) {
!strings.Contains(rr.Body.String(), "/admin/tasks") ||
!strings.Contains(rr.Body.String(), "/admin/parse/text-units") ||
!strings.Contains(rr.Body.String(), "translation_results") ||
!strings.Contains(rr.Body.String(), "task-cancel") {
!strings.Contains(rr.Body.String(), "task-cancel") ||
!strings.Contains(rr.Body.String(), "/admin/translation/memory/query") ||
!strings.Contains(rr.Body.String(), "translation-memory-confirm") {
t.Fatalf("openapi missing dashboard/task admin routes")
}
@@ -1524,6 +1610,8 @@ func TestOpenAPIAndAdminReservedEndpoints(t *testing.T) {
links := strings.Join(admin.Links, "\n")
if !strings.Contains(links, "/admin/translation/tasks") ||
!strings.Contains(links, "/admin/translation/handoff") ||
!strings.Contains(links, "/admin/translation/memory/summary") ||
!strings.Contains(links, "/admin/translation/memory/query") ||
!strings.Contains(links, "/admin/dashboard/") ||
!strings.Contains(links, "/admin/parse/text-units") ||
!strings.Contains(links, "/admin/tasks/logs") {
@@ -1729,6 +1817,7 @@ func TestAdminControlForwardsAllowlistedActions(t *testing.T) {
{name: "translation task update", action: "translation-task-update", body: `{"task_id":"textunit/v-current/Scenario","status":"completed","provider":"manual","provider_run_id":"manual-run-1","translation_results":[{"unit_id":"direct:a#unit:0","source_text":"source","translated_text":"译文"}]}`, rpcMethod: "translation.task.update", call: "translation.task.update"},
{name: "translation worker run", action: "translation-worker-run", body: `{"provider":"mock","concurrency":8,"max_tasks":2,"retry_backoff_seconds":0,"worker_id":"dashboard-worker"}`, rpcMethod: "translation.worker.run", call: "translation.worker.run"},
{name: "translation proofread", action: "translation-proofread", rpcMethod: "translation.proofread", call: "translation.proofread"},
{name: "translation memory confirm", action: "translation-memory-confirm", body: `{"record_id":"tm-record-1","reviewer":"reviewer","reason":"reviewed"}`, rpcMethod: "translation.memory.confirm", call: "translation.memory.confirm"},
{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 rollback", action: "localized-rollback", body: `{"localized_release_id":"localized-1"}`, rpcMethod: "localized.rollback", call: "localized.rollback"},
}
@@ -1760,6 +1849,11 @@ func TestAdminControlForwardsAllowlistedActions(t *testing.T) {
backend.translationTaskUpdates[0].TranslationResults[0].TranslatedText != "译文" {
t.Fatalf("translation task updates=%#v", backend.translationTaskUpdates)
}
if len(backend.translationMemoryConfirmParams) != 1 ||
backend.translationMemoryConfirmParams[0].RecordID != "tm-record-1" ||
backend.translationMemoryConfirmParams[0].Reviewer != "reviewer" {
t.Fatalf("TM confirm params=%#v", backend.translationMemoryConfirmParams)
}
request := httptest.NewRequest(http.MethodPost, "/admin/control/translation-task-update", strings.NewReader(`{"task_id":""}`))
request.Header.Set("Authorization", "Bearer control-token")
@@ -1785,6 +1879,14 @@ func TestAdminControlForwardsAllowlistedActions(t *testing.T) {
t.Fatalf("invalid translation worker status=%d body=%s", recorder.Code, recorder.Body.String())
}
request = httptest.NewRequest(http.MethodPost, "/admin/control/translation-memory-confirm", strings.NewReader(`{"record_id":""}`))
request.Header.Set("Authorization", "Bearer control-token")
recorder = httptest.NewRecorder()
s.Handler().ServeHTTP(recorder, request)
if recorder.Code != http.StatusBadRequest {
t.Fatalf("invalid TM confirm status=%d body=%s", recorder.Code, recorder.Body.String())
}
request = httptest.NewRequest(http.MethodPost, "/admin/control/localized-publish", strings.NewReader(`{"from_worker":true,"translation_file":"/tmp/workbench.json"}`))
request.Header.Set("Authorization", "Bearer control-token")
recorder = httptest.NewRecorder()
+88
View File
@@ -110,3 +110,91 @@ func TestRustContractFixturesPreserveGoMirror(t *testing.T) {
t.Fatalf("legacy game_main_config field unexpectedly present: %s", snapshot.LegacyGameMainConfig)
}
}
func TestTranslationMemoryRustContractMirror(t *testing.T) {
raw := []byte(`{
"available": true,
"path": "${TM_PATH}",
"source_text": "${SOURCE_TEXT}",
"source_context": {
"destination": "${DESTINATION}",
"field_path": "${FIELD_PATH}"
},
"matches": [{
"entry": {
"record_id": "${RECORD_ID}",
"source_text": "${SOURCE_TEXT}",
"source_hash": "${SOURCE_HASH}",
"normalized_source_text": "${NORMALIZED_SOURCE_TEXT}",
"source_context": {
"destination": "${DESTINATION}",
"field_path": "${FIELD_PATH}"
},
"source_context_hash": "${SOURCE_CONTEXT_HASH}",
"translated_text": "${TRANSLATED_TEXT}",
"translation_source_kind": "provider",
"trust_status": "trusted",
"official_release_id": "${OFFICIAL_RELEASE_ID}",
"source_trace": {
"official_release_id": "${OFFICIAL_RELEASE_ID}",
"unit_id": "${UNIT_ID}",
"task_id": "${TASK_ID}",
"destination": "${DESTINATION}",
"archive_entry": "${ARCHIVE_ENTRY}",
"serialized_file": "${SERIALIZED_FILE}",
"path_id": 42,
"class_id": 114,
"field_path": "${FIELD_PATH}",
"format": "json",
"asset_name": "${ASSET_NAME}",
"text_source_kind": "text_asset"
},
"provider": "${PROVIDER}",
"provider_run_id": "${PROVIDER_RUN_ID}",
"created_unix_seconds": 100,
"updated_unix_seconds": 200,
"trusted_unix_seconds": 200,
"trusted_by": "${REVIEWER}",
"trusted_reason": "${TRUST_REASON}"
},
"match_kind": "strong_exact",
"can_auto_reuse": true
}]
}`)
if bytes.Contains(raw, []byte("/tmp/")) {
t.Fatal("TM mirror contains a local temporary path")
}
var report backendrpc.TranslationMemoryQueryReport
if err := json.Unmarshal(raw, &report); err != nil {
t.Fatalf("decode TM query mirror: %v", err)
}
if !report.Available || report.Path != "${TM_PATH}" ||
report.SourceText != "${SOURCE_TEXT}" ||
report.SourceContext["field_path"] != "${FIELD_PATH}" ||
len(report.Matches) != 1 {
t.Fatalf("TM report=%+v", report)
}
match := report.Matches[0]
if match.MatchKind != "strong_exact" || !match.CanAutoReuse ||
match.Entry.TrustStatus != "trusted" ||
match.Entry.TranslationSourceKind != "provider" ||
match.Entry.SourceTrace.PathID == nil || *match.Entry.SourceTrace.PathID != 42 ||
match.Entry.SourceTrace.ClassID == nil || *match.Entry.SourceTrace.ClassID != 114 ||
match.Entry.TrustedBy == nil || *match.Entry.TrustedBy != "${REVIEWER}" {
t.Fatalf("TM match=%+v", match)
}
var missing backendrpc.TranslationMemorySummaryReport
if err := json.Unmarshal([]byte(`{
"available": false,
"path": "${TM_PATH}",
"reason": "database_missing"
}`), &missing); err != nil {
t.Fatalf("decode missing TM summary mirror: %v", err)
}
if missing.Available || missing.Summary != nil || missing.SchemaVersion != nil ||
missing.Reason != "database_missing" {
t.Fatalf("missing TM summary=%+v", missing)
}
}
+59 -1
View File
@@ -411,6 +411,56 @@ paths:
description: Missing or invalid admin token.
"503":
description: Rust bat translation backend is unavailable.
/admin/translation/memory/summary:
get:
summary: Read Rust-owned Translation Memory summary
parameters:
- name: translation_memory_path
in: query
schema:
type: string
responses:
"200":
description: Translation Memory availability and candidate/trusted counts.
"401":
description: Missing or invalid admin token.
"503":
description: Rust bat Translation Memory backend is unavailable.
/admin/translation/memory/query:
get:
summary: Query Rust-owned Translation Memory records
parameters:
- name: source_text
in: query
required: true
schema:
type: string
- name: source_context
in: query
description: JSON object whose values are strings.
schema:
type: string
- name: limit
in: query
schema:
type: integer
format: int64
minimum: 1
maximum: 1000
default: 100
- name: translation_memory_path
in: query
schema:
type: string
responses:
"200":
description: Translation Memory matches with reuse decision and provenance.
"400":
description: Missing source text or invalid context/limit.
"401":
description: Missing or invalid admin token.
"503":
description: Rust bat Translation Memory backend is unavailable.
/admin/translation/status:
get:
summary: Read Rust-owned localized release status
@@ -430,7 +480,7 @@ paths:
required: true
schema:
type: string
enum: [reload, refresh, restart, sync, verify, repair, catalog-refresh, schedule-add, schedule-update, schedule-remove, schedule-run, task-cancel, translation-task-update, translation-worker-run, translation-proofread, localized-publish, localized-rollback]
enum: [reload, refresh, restart, sync, verify, repair, catalog-refresh, schedule-add, schedule-update, schedule-remove, schedule-run, task-cancel, translation-task-update, translation-worker-run, translation-proofread, translation-memory-confirm, localized-publish, localized-rollback]
requestBody:
required: false
content:
@@ -521,6 +571,14 @@ paths:
minimum: 1
worker_id:
type: string
translation_memory_path:
type: string
record_id:
type: string
reviewer:
type: string
reason:
type: string
translation_file:
type: string
from_worker:
+21
View File
@@ -83,6 +83,15 @@ type TranslationBackend interface {
TranslationProofread(ctx context.Context) (json.RawMessage, error)
}
// TranslationMemoryBackend exposes the Rust-owned Translation Memory query and
// explicit confirmation operations. Go forwards typed requests and responses
// but never opens or mutates the TM database itself.
type TranslationMemoryBackend interface {
TranslationMemorySummary(ctx context.Context, params backendrpc.TranslationMemorySummaryParams) (*backendrpc.TranslationMemorySummaryReport, error)
TranslationMemoryQuery(ctx context.Context, params backendrpc.TranslationMemoryQueryParams) (*backendrpc.TranslationMemoryQueryReport, error)
TranslationMemoryConfirm(ctx context.Context, params backendrpc.TranslationMemoryConfirmParams) (*backendrpc.TranslationMemoryConfirmReport, error)
}
// LocalizedBackend exposes localized release status and the explicit
// publish/rollback controls used by the authenticated dashboard.
type LocalizedBackend interface {
@@ -182,6 +191,18 @@ func (r RPCClient) TranslationProofread(ctx context.Context) (json.RawMessage, e
return r.Client.TranslationProofread(ctx)
}
func (r RPCClient) TranslationMemorySummary(ctx context.Context, params backendrpc.TranslationMemorySummaryParams) (*backendrpc.TranslationMemorySummaryReport, error) {
return r.Client.TranslationMemorySummary(ctx, params)
}
func (r RPCClient) TranslationMemoryQuery(ctx context.Context, params backendrpc.TranslationMemoryQueryParams) (*backendrpc.TranslationMemoryQueryReport, error) {
return r.Client.TranslationMemoryQuery(ctx, params)
}
func (r RPCClient) TranslationMemoryConfirm(ctx context.Context, params backendrpc.TranslationMemoryConfirmParams) (*backendrpc.TranslationMemoryConfirmReport, error) {
return r.Client.TranslationMemoryConfirm(ctx, params)
}
func (r RPCClient) LocalizedStatus(ctx context.Context) (json.RawMessage, error) {
return r.Client.LocalizedStatus(ctx)
}
+4
View File
@@ -74,6 +74,8 @@ func (s *Server) Handler() http.Handler {
mux.HandleFunc("/admin/schedules", s.handleAdminSchedules)
mux.HandleFunc("/admin/translation/tasks", s.handleAdminTranslationTasks)
mux.HandleFunc("/admin/translation/handoff", s.handleAdminTranslationHandoff)
mux.HandleFunc("/admin/translation/memory/summary", s.handleAdminTranslationMemorySummary)
mux.HandleFunc("/admin/translation/memory/query", s.handleAdminTranslationMemoryQuery)
mux.HandleFunc("/admin/translation/status", s.handleAdminLocalizedStatus)
mux.HandleFunc("/admin/control/", s.handleAdminControl)
mux.HandleFunc("/admin/", s.handleAdminIndex)
@@ -151,6 +153,8 @@ func (s *Server) handleRoot(w http.ResponseWriter, r *http.Request) {
"/admin/schedules",
"/admin/translation/tasks",
"/admin/translation/handoff",
"/admin/translation/memory/summary",
"/admin/translation/memory/query",
"/admin/translation/status",
"/admin/control/{action}",
},