2
0

feat(i18n): add bulk AI translation for all languages
Some checks failed
Build and Release / Create Release (push) Successful in 1s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 3m21s
Build and Release / Lint (push) Failing after 8m46s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been skipped
Build and Release / Unit Tests (push) Successful in 8m49s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been skipped
Build and Release / Build Binary (linux/arm64) (push) Has been skipped

Add "Translate All (AI)" button to pages language settings that translates all configured languages in one operation. Shows success/partial success messages with counts. Adds locale keys for all 29 languages. Also removes trailing newlines from locale files for consistency.
This commit is contained in:
2026-03-17 11:35:20 -04:00
parent d02f25c0ba
commit 43e490d933
45 changed files with 505 additions and 256 deletions

View File

@@ -1070,6 +1070,8 @@ func deepMergeConfig(base *pages_module.LandingConfig, overrideJSON string) (*pa
}
// deepMerge recursively merges src into dst.
// Maps are merged recursively; arrays are merged element-wise (preserving
// base fields like icons that the translation overlay may omit).
func deepMerge(dst, src map[string]any) map[string]any {
for key, srcVal := range src {
if dstVal, ok := dst[key]; ok {
@@ -1080,12 +1082,47 @@ func deepMerge(dst, src map[string]any) map[string]any {
dst[key] = deepMerge(dstMap, srcMap)
continue
}
// Both are arrays: merge element-wise
srcArr, srcIsArr := srcVal.([]any)
dstArr, dstIsArr := dstVal.([]any)
if srcIsArr && dstIsArr {
dst[key] = deepMergeArrays(dstArr, srcArr)
continue
}
}
dst[key] = srcVal
}
return dst
}
// deepMergeArrays merges two arrays element-wise. For each index, if both
// elements are maps, they are deep-merged (so translation fields override
// base fields while preserving untranslated fields like icons). Otherwise
// the source element replaces the base.
func deepMergeArrays(dst, src []any) []any {
result := make([]any, max(len(dst), len(src)))
for i := range result {
if i < len(src) && i < len(dst) {
srcMap, srcIsMap := src[i].(map[string]any)
dstMap, dstIsMap := dst[i].(map[string]any)
if srcIsMap && dstIsMap {
merged := make(map[string]any)
for k, v := range dstMap {
merged[k] = v
}
result[i] = deepMerge(merged, srcMap)
} else {
result[i] = src[i]
}
} else if i < len(src) {
result[i] = src[i]
} else {
result[i] = dst[i]
}
}
return result
}
// detectPageLanguage determines the active language for a landing page.
// Priority: ?lang= query param > pages_lang cookie > Accept-Language header > default.
func detectPageLanguage(ctx *context.Context, config *pages_module.LandingConfig) string {

View File

@@ -1305,6 +1305,57 @@ func PagesLanguagesPost(ctx *context.Context) {
}
}
ctx.Flash.Success(ctx.Tr("repo.settings.pages.ai_translate_success"))
case "ai_translate_all":
defaultLang := config.I18n.DefaultLang
if defaultLang == "" {
defaultLang = "en"
}
var successCount, failCount int
for _, lang := range config.I18n.Languages {
if lang == defaultLang {
continue
}
translated, err := pages_service.TranslateLandingPageContent(ctx, ctx.Repo.Repository, config, lang)
if err != nil {
log.Error("AI translation failed for %s: %v", lang, err)
failCount++
continue
}
existing, err := pages_model.GetTranslation(ctx, ctx.Repo.Repository.ID, lang)
if err != nil {
log.Error("GetTranslation failed for %s: %v", lang, err)
failCount++
continue
}
if existing != nil {
existing.ConfigJSON = translated
existing.AutoGenerated = true
if err := pages_model.UpdateTranslation(ctx, existing); err != nil {
log.Error("UpdateTranslation failed for %s: %v", lang, err)
failCount++
continue
}
} else {
t := &pages_model.Translation{
RepoID: ctx.Repo.Repository.ID,
Lang: lang,
ConfigJSON: translated,
AutoGenerated: true,
}
if err := pages_model.CreateTranslation(ctx, t); err != nil {
log.Error("CreateTranslation failed for %s: %v", lang, err)
failCount++
continue
}
}
successCount++
}
if failCount == 0 {
ctx.Flash.Success(ctx.Tr("repo.settings.pages.ai_translate_all_success", successCount))
} else {
ctx.Flash.Warning(ctx.Tr("repo.settings.pages.ai_translate_all_partial", successCount, successCount+failCount, failCount))
}
}
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/pages/languages")