feat(i18n): add translation keys for new pages sections
Some checks failed
Build and Release / Create Release (push) Successful in 1s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m41s
Build and Release / Unit Tests (push) Successful in 8m55s
Build and Release / Lint (push) Successful in 9m23s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Failing after 1m4s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Failing after 9h0m35s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 50s
Build and Release / Build Binary (linux/arm64) (push) Failing after 32s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Failing after 54s
Some checks failed
Build and Release / Create Release (push) Successful in 1s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m41s
Build and Release / Unit Tests (push) Successful in 8m55s
Build and Release / Lint (push) Successful in 9m23s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Failing after 1m4s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Failing after 9h0m35s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 50s
Build and Release / Build Binary (linux/arm64) (push) Failing after 32s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Failing after 54s
Add locale keys for translating new landing page sections: gallery, comparison, blog, and expanded fields for stats, pricing, testimonials, and footer. Adds section headers and field labels for translation UI. Includes keys for all 29 supported languages. Enables full localization of new pages features added in recent commits.
This commit is contained in:
@@ -716,19 +716,89 @@ func PagesThemePost(ctx *context.Context) {
|
||||
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/pages/theme")
|
||||
}
|
||||
|
||||
// TranslationView is a flattened view of a translation for the settings UI
|
||||
// TranslationView is a flattened view of a translation for the settings UI.
|
||||
// Slice fields are padded to match the base config array lengths.
|
||||
type TranslationView struct {
|
||||
Headline string
|
||||
Subheadline string
|
||||
PrimaryCTA string
|
||||
SecondaryCTA string
|
||||
// Brand
|
||||
BrandName string
|
||||
BrandTagline string
|
||||
// Hero
|
||||
Headline string
|
||||
Subheadline string
|
||||
PrimaryCTA string
|
||||
SecondaryCTA string
|
||||
// Stats (parallel to Config.Stats)
|
||||
StatsValues []string
|
||||
StatsLabels []string
|
||||
// Value Props (parallel to Config.ValueProps)
|
||||
ValuePropTitles []string
|
||||
ValuePropDescs []string
|
||||
// Features (parallel to Config.Features)
|
||||
FeatureTitles []string
|
||||
FeatureDescs []string
|
||||
// Testimonials (parallel to Config.SocialProof.Testimonials)
|
||||
TestimonialQuotes []string
|
||||
TestimonialRoles []string
|
||||
// Pricing
|
||||
PricingHeadline string
|
||||
PricingSubheadline string
|
||||
PlanNames []string
|
||||
PlanPeriods []string
|
||||
PlanCTAs []string
|
||||
// CTA Section
|
||||
CTAHeadline string
|
||||
CTASubheadline string
|
||||
CTAButton string
|
||||
// Blog
|
||||
BlogHeadline string
|
||||
BlogSubheadline string
|
||||
BlogCTAButton string
|
||||
// Gallery
|
||||
GalleryHeadline string
|
||||
GallerySubheadline string
|
||||
// Comparison
|
||||
ComparisonHeadline string
|
||||
ComparisonSubheadline string
|
||||
// Footer
|
||||
FooterCopyright string
|
||||
FooterLinkLabels []string
|
||||
// SEO
|
||||
SEOTitle string
|
||||
SEODescription string
|
||||
}
|
||||
|
||||
// parseTranslationView parses a PagesTranslation into a flat view for the template
|
||||
func parseTranslationView(t *pages_model.Translation) *TranslationView {
|
||||
// overlayString extracts a string from a map
|
||||
func overlayString(m map[string]any, key string) string {
|
||||
if v, ok := m[key].(string); ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// overlayStringSlice extracts a []string-field slice from an overlay array of objects.
|
||||
// Each array element is a map; fieldKey is the key to extract from each map.
|
||||
func overlayStringSlice(overlay map[string]any, arrayKey, fieldKey string, padLen int) []string {
|
||||
result := make([]string, padLen)
|
||||
arr, ok := overlay[arrayKey].([]any)
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
for i, item := range arr {
|
||||
if i >= padLen {
|
||||
break
|
||||
}
|
||||
if m, ok := item.(map[string]any); ok {
|
||||
if v, ok := m[fieldKey].(string); ok {
|
||||
result[i] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// parseTranslationView parses a PagesTranslation into a flat view for the template.
|
||||
// config is used to determine array lengths for padding slices.
|
||||
func parseTranslationView(t *pages_model.Translation, config *pages_module.LandingConfig) *TranslationView {
|
||||
if t == nil || t.ConfigJSON == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -738,37 +808,135 @@ func parseTranslationView(t *pages_model.Translation) *TranslationView {
|
||||
}
|
||||
|
||||
view := &TranslationView{}
|
||||
|
||||
// Brand
|
||||
if brand, ok := overlay["brand"].(map[string]any); ok {
|
||||
view.BrandName = overlayString(brand, "name")
|
||||
view.BrandTagline = overlayString(brand, "tagline")
|
||||
}
|
||||
|
||||
// Hero
|
||||
if hero, ok := overlay["hero"].(map[string]any); ok {
|
||||
if v, ok := hero["headline"].(string); ok {
|
||||
view.Headline = v
|
||||
}
|
||||
if v, ok := hero["subheadline"].(string); ok {
|
||||
view.Subheadline = v
|
||||
}
|
||||
view.Headline = overlayString(hero, "headline")
|
||||
view.Subheadline = overlayString(hero, "subheadline")
|
||||
if cta, ok := hero["primary_cta"].(map[string]any); ok {
|
||||
if v, ok := cta["label"].(string); ok {
|
||||
view.PrimaryCTA = v
|
||||
}
|
||||
view.PrimaryCTA = overlayString(cta, "label")
|
||||
}
|
||||
if cta, ok := hero["secondary_cta"].(map[string]any); ok {
|
||||
if v, ok := cta["label"].(string); ok {
|
||||
view.SecondaryCTA = v
|
||||
view.SecondaryCTA = overlayString(cta, "label")
|
||||
}
|
||||
}
|
||||
|
||||
// Stats
|
||||
view.StatsValues = overlayStringSlice(overlay, "stats", "value", len(config.Stats))
|
||||
view.StatsLabels = overlayStringSlice(overlay, "stats", "label", len(config.Stats))
|
||||
|
||||
// Value Props
|
||||
view.ValuePropTitles = overlayStringSlice(overlay, "value_props", "title", len(config.ValueProps))
|
||||
view.ValuePropDescs = overlayStringSlice(overlay, "value_props", "description", len(config.ValueProps))
|
||||
|
||||
// Features
|
||||
view.FeatureTitles = overlayStringSlice(overlay, "features", "title", len(config.Features))
|
||||
view.FeatureDescs = overlayStringSlice(overlay, "features", "description", len(config.Features))
|
||||
|
||||
// Testimonials (stored under social_proof.testimonials)
|
||||
view.TestimonialQuotes = make([]string, len(config.SocialProof.Testimonials))
|
||||
view.TestimonialRoles = make([]string, len(config.SocialProof.Testimonials))
|
||||
if sp, ok := overlay["social_proof"].(map[string]any); ok {
|
||||
if arr, ok := sp["testimonials"].([]any); ok {
|
||||
for i, item := range arr {
|
||||
if i >= len(config.SocialProof.Testimonials) {
|
||||
break
|
||||
}
|
||||
if m, ok := item.(map[string]any); ok {
|
||||
view.TestimonialQuotes[i] = overlayString(m, "quote")
|
||||
view.TestimonialRoles[i] = overlayString(m, "role")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pricing
|
||||
if pricing, ok := overlay["pricing"].(map[string]any); ok {
|
||||
view.PricingHeadline = overlayString(pricing, "headline")
|
||||
view.PricingSubheadline = overlayString(pricing, "subheadline")
|
||||
if plans, ok := pricing["plans"].([]any); ok {
|
||||
view.PlanNames = make([]string, len(config.Pricing.Plans))
|
||||
view.PlanPeriods = make([]string, len(config.Pricing.Plans))
|
||||
view.PlanCTAs = make([]string, len(config.Pricing.Plans))
|
||||
for i, item := range plans {
|
||||
if i >= len(config.Pricing.Plans) {
|
||||
break
|
||||
}
|
||||
if m, ok := item.(map[string]any); ok {
|
||||
view.PlanNames[i] = overlayString(m, "name")
|
||||
view.PlanPeriods[i] = overlayString(m, "period")
|
||||
view.PlanCTAs[i] = overlayString(m, "cta")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
view.PlanNames = make([]string, len(config.Pricing.Plans))
|
||||
view.PlanPeriods = make([]string, len(config.Pricing.Plans))
|
||||
view.PlanCTAs = make([]string, len(config.Pricing.Plans))
|
||||
}
|
||||
} else {
|
||||
view.PlanNames = make([]string, len(config.Pricing.Plans))
|
||||
view.PlanPeriods = make([]string, len(config.Pricing.Plans))
|
||||
view.PlanCTAs = make([]string, len(config.Pricing.Plans))
|
||||
}
|
||||
|
||||
// CTA Section
|
||||
if ctaSec, ok := overlay["cta_section"].(map[string]any); ok {
|
||||
if v, ok := ctaSec["headline"].(string); ok {
|
||||
view.CTAHeadline = v
|
||||
}
|
||||
if v, ok := ctaSec["subheadline"].(string); ok {
|
||||
view.CTASubheadline = v
|
||||
}
|
||||
view.CTAHeadline = overlayString(ctaSec, "headline")
|
||||
view.CTASubheadline = overlayString(ctaSec, "subheadline")
|
||||
if btn, ok := ctaSec["button"].(map[string]any); ok {
|
||||
if v, ok := btn["label"].(string); ok {
|
||||
view.CTAButton = v
|
||||
view.CTAButton = overlayString(btn, "label")
|
||||
}
|
||||
}
|
||||
|
||||
// Blog
|
||||
if blog, ok := overlay["blog"].(map[string]any); ok {
|
||||
view.BlogHeadline = overlayString(blog, "headline")
|
||||
view.BlogSubheadline = overlayString(blog, "subheadline")
|
||||
if btn, ok := blog["cta_button"].(map[string]any); ok {
|
||||
view.BlogCTAButton = overlayString(btn, "label")
|
||||
}
|
||||
}
|
||||
|
||||
// Gallery
|
||||
if gallery, ok := overlay["gallery"].(map[string]any); ok {
|
||||
view.GalleryHeadline = overlayString(gallery, "headline")
|
||||
view.GallerySubheadline = overlayString(gallery, "subheadline")
|
||||
}
|
||||
|
||||
// Comparison
|
||||
if comp, ok := overlay["comparison"].(map[string]any); ok {
|
||||
view.ComparisonHeadline = overlayString(comp, "headline")
|
||||
view.ComparisonSubheadline = overlayString(comp, "subheadline")
|
||||
}
|
||||
|
||||
// Footer
|
||||
view.FooterLinkLabels = make([]string, len(config.Footer.Links))
|
||||
if footer, ok := overlay["footer"].(map[string]any); ok {
|
||||
view.FooterCopyright = overlayString(footer, "copyright")
|
||||
if links, ok := footer["links"].([]any); ok {
|
||||
for i, item := range links {
|
||||
if i >= len(config.Footer.Links) {
|
||||
break
|
||||
}
|
||||
if m, ok := item.(map[string]any); ok {
|
||||
view.FooterLinkLabels[i] = overlayString(m, "label")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SEO
|
||||
if seo, ok := overlay["seo"].(map[string]any); ok {
|
||||
view.SEOTitle = overlayString(seo, "title")
|
||||
view.SEODescription = overlayString(seo, "description")
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
@@ -776,6 +944,19 @@ func parseTranslationView(t *pages_model.Translation) *TranslationView {
|
||||
func buildTranslationJSON(ctx *context.Context) string {
|
||||
overlay := map[string]any{}
|
||||
|
||||
// Brand
|
||||
brand := map[string]any{}
|
||||
if v := ctx.FormString("trans_brand_name"); v != "" {
|
||||
brand["name"] = v
|
||||
}
|
||||
if v := ctx.FormString("trans_brand_tagline"); v != "" {
|
||||
brand["tagline"] = v
|
||||
}
|
||||
if len(brand) > 0 {
|
||||
overlay["brand"] = brand
|
||||
}
|
||||
|
||||
// Hero
|
||||
hero := map[string]any{}
|
||||
if v := ctx.FormString("trans_headline"); v != "" {
|
||||
hero["headline"] = v
|
||||
@@ -793,6 +974,103 @@ func buildTranslationJSON(ctx *context.Context) string {
|
||||
overlay["hero"] = hero
|
||||
}
|
||||
|
||||
// Stats (indexed)
|
||||
var stats []map[string]any
|
||||
for i := range 20 {
|
||||
v := ctx.FormString(fmt.Sprintf("trans_stat_%d_value", i))
|
||||
l := ctx.FormString(fmt.Sprintf("trans_stat_%d_label", i))
|
||||
if v == "" && l == "" {
|
||||
// Check if there are more by looking ahead
|
||||
if ctx.FormString(fmt.Sprintf("trans_stat_%d_value", i+1)) == "" &&
|
||||
ctx.FormString(fmt.Sprintf("trans_stat_%d_label", i+1)) == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
stats = append(stats, map[string]any{"value": v, "label": l})
|
||||
}
|
||||
if len(stats) > 0 {
|
||||
overlay["stats"] = stats
|
||||
}
|
||||
|
||||
// Value Props (indexed)
|
||||
var valueProps []map[string]any
|
||||
for i := range 20 {
|
||||
t := ctx.FormString(fmt.Sprintf("trans_vp_%d_title", i))
|
||||
d := ctx.FormString(fmt.Sprintf("trans_vp_%d_desc", i))
|
||||
if t == "" && d == "" {
|
||||
if ctx.FormString(fmt.Sprintf("trans_vp_%d_title", i+1)) == "" &&
|
||||
ctx.FormString(fmt.Sprintf("trans_vp_%d_desc", i+1)) == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
valueProps = append(valueProps, map[string]any{"title": t, "description": d})
|
||||
}
|
||||
if len(valueProps) > 0 {
|
||||
overlay["value_props"] = valueProps
|
||||
}
|
||||
|
||||
// Features (indexed)
|
||||
var features []map[string]any
|
||||
for i := range 20 {
|
||||
t := ctx.FormString(fmt.Sprintf("trans_feat_%d_title", i))
|
||||
d := ctx.FormString(fmt.Sprintf("trans_feat_%d_desc", i))
|
||||
if t == "" && d == "" {
|
||||
if ctx.FormString(fmt.Sprintf("trans_feat_%d_title", i+1)) == "" &&
|
||||
ctx.FormString(fmt.Sprintf("trans_feat_%d_desc", i+1)) == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
features = append(features, map[string]any{"title": t, "description": d})
|
||||
}
|
||||
if len(features) > 0 {
|
||||
overlay["features"] = features
|
||||
}
|
||||
|
||||
// Testimonials (indexed)
|
||||
var testimonials []map[string]any
|
||||
for i := range 20 {
|
||||
q := ctx.FormString(fmt.Sprintf("trans_test_%d_quote", i))
|
||||
r := ctx.FormString(fmt.Sprintf("trans_test_%d_role", i))
|
||||
if q == "" && r == "" {
|
||||
if ctx.FormString(fmt.Sprintf("trans_test_%d_quote", i+1)) == "" &&
|
||||
ctx.FormString(fmt.Sprintf("trans_test_%d_role", i+1)) == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
testimonials = append(testimonials, map[string]any{"quote": q, "role": r})
|
||||
}
|
||||
if len(testimonials) > 0 {
|
||||
overlay["social_proof"] = map[string]any{"testimonials": testimonials}
|
||||
}
|
||||
|
||||
// Pricing
|
||||
pricing := map[string]any{}
|
||||
if v := ctx.FormString("trans_pricing_headline"); v != "" {
|
||||
pricing["headline"] = v
|
||||
}
|
||||
if v := ctx.FormString("trans_pricing_subheadline"); v != "" {
|
||||
pricing["subheadline"] = v
|
||||
}
|
||||
var plans []map[string]any
|
||||
for i := range 10 {
|
||||
n := ctx.FormString(fmt.Sprintf("trans_plan_%d_name", i))
|
||||
p := ctx.FormString(fmt.Sprintf("trans_plan_%d_period", i))
|
||||
c := ctx.FormString(fmt.Sprintf("trans_plan_%d_cta", i))
|
||||
if n == "" && p == "" && c == "" {
|
||||
if ctx.FormString(fmt.Sprintf("trans_plan_%d_name", i+1)) == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
plans = append(plans, map[string]any{"name": n, "period": p, "cta": c})
|
||||
}
|
||||
if len(plans) > 0 {
|
||||
pricing["plans"] = plans
|
||||
}
|
||||
if len(pricing) > 0 {
|
||||
overlay["pricing"] = pricing
|
||||
}
|
||||
|
||||
// CTA Section
|
||||
ctaSec := map[string]any{}
|
||||
if v := ctx.FormString("trans_cta_headline"); v != "" {
|
||||
ctaSec["headline"] = v
|
||||
@@ -807,6 +1085,79 @@ func buildTranslationJSON(ctx *context.Context) string {
|
||||
overlay["cta_section"] = ctaSec
|
||||
}
|
||||
|
||||
// Blog
|
||||
blog := map[string]any{}
|
||||
if v := ctx.FormString("trans_blog_headline"); v != "" {
|
||||
blog["headline"] = v
|
||||
}
|
||||
if v := ctx.FormString("trans_blog_subheadline"); v != "" {
|
||||
blog["subheadline"] = v
|
||||
}
|
||||
if v := ctx.FormString("trans_blog_cta"); v != "" {
|
||||
blog["cta_button"] = map[string]any{"label": v}
|
||||
}
|
||||
if len(blog) > 0 {
|
||||
overlay["blog"] = blog
|
||||
}
|
||||
|
||||
// Gallery
|
||||
gallery := map[string]any{}
|
||||
if v := ctx.FormString("trans_gallery_headline"); v != "" {
|
||||
gallery["headline"] = v
|
||||
}
|
||||
if v := ctx.FormString("trans_gallery_subheadline"); v != "" {
|
||||
gallery["subheadline"] = v
|
||||
}
|
||||
if len(gallery) > 0 {
|
||||
overlay["gallery"] = gallery
|
||||
}
|
||||
|
||||
// Comparison
|
||||
comp := map[string]any{}
|
||||
if v := ctx.FormString("trans_comparison_headline"); v != "" {
|
||||
comp["headline"] = v
|
||||
}
|
||||
if v := ctx.FormString("trans_comparison_subheadline"); v != "" {
|
||||
comp["subheadline"] = v
|
||||
}
|
||||
if len(comp) > 0 {
|
||||
overlay["comparison"] = comp
|
||||
}
|
||||
|
||||
// Footer
|
||||
footer := map[string]any{}
|
||||
if v := ctx.FormString("trans_footer_copyright"); v != "" {
|
||||
footer["copyright"] = v
|
||||
}
|
||||
var footerLinks []map[string]any
|
||||
for i := range 20 {
|
||||
l := ctx.FormString(fmt.Sprintf("trans_footer_link_%d", i))
|
||||
if l == "" {
|
||||
if ctx.FormString(fmt.Sprintf("trans_footer_link_%d", i+1)) == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
footerLinks = append(footerLinks, map[string]any{"label": l})
|
||||
}
|
||||
if len(footerLinks) > 0 {
|
||||
footer["links"] = footerLinks
|
||||
}
|
||||
if len(footer) > 0 {
|
||||
overlay["footer"] = footer
|
||||
}
|
||||
|
||||
// SEO
|
||||
seo := map[string]any{}
|
||||
if v := ctx.FormString("trans_seo_title"); v != "" {
|
||||
seo["title"] = v
|
||||
}
|
||||
if v := ctx.FormString("trans_seo_description"); v != "" {
|
||||
seo["description"] = v
|
||||
}
|
||||
if len(seo) > 0 {
|
||||
overlay["seo"] = seo
|
||||
}
|
||||
|
||||
if len(overlay) == 0 {
|
||||
return ""
|
||||
}
|
||||
@@ -835,7 +1186,7 @@ func PagesLanguages(ctx *context.Context) {
|
||||
translations, err := pages_model.GetTranslationsByRepoID(ctx, ctx.Repo.Repository.ID)
|
||||
if err == nil {
|
||||
for _, t := range translations {
|
||||
translationMap[t.Lang] = parseTranslationView(t)
|
||||
translationMap[t.Lang] = parseTranslationView(t, config)
|
||||
}
|
||||
}
|
||||
ctx.Data["TranslationMap"] = translationMap
|
||||
|
||||
Reference in New Issue
Block a user