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
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:
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user