package localization import ( "RenewCMS/internal/domain/language" "regexp" "slices" "strings" ) var socialLocalePattern = regexp.MustCompile(`^([A-Za-z]{2,3})_([A-Za-z]{2})$`) var regionPattern = regexp.MustCompile(`^[A-Za-z]{2}$`) func NormalizeSocialLocale(tag string, social *string) (*string, error) { if social == nil { return nil, nil } parts := socialLocalePattern.FindStringSubmatch(*social) if parts == nil { return nil, ErrInvalidSocialLocale } tagLanguage, _, _ := strings.Cut(tag, "-") if !strings.EqualFold(parts[1], tagLanguage) { return nil, ErrInvalidSocialLocale } normalized := strings.ToLower(parts[1]) + "_" + strings.ToUpper(parts[2]) return &normalized, nil } func SocialLocaleState(row Row, siteLocale *string) string { if row.SocialLocale != nil { return SocialLocaleSet } if EffectiveSocialLocale(row, siteLocale) != nil { return SocialLocaleDerived } return SocialLocaleNotSet } func EffectiveSocialLocale(row Row, siteLocale *string) *string { if row.SocialLocale != nil { return row.SocialLocale } if _, region, found := strings.Cut(row.Tag, "-"); found && region != "" { derived := strings.Replace(row.Tag, "-", "_", 1) return &derived } return borrowedSocialLocale(row.Tag, siteLocale) } func borrowedSocialLocale(tag string, siteLocale *string) *string { if siteLocale == nil { return nil } tagLanguage, _, _ := strings.Cut(tag, "-") parts := strings.Split(*siteLocale, "-") if len(parts) < 2 || !strings.EqualFold(parts[0], tagLanguage) || !regionPattern.MatchString(parts[1]) { return nil } borrowed := strings.ToLower(parts[0]) + "_" + strings.ToUpper(parts[1]) return &borrowed } func normalizeEntries(entries []Entry) ([]Entry, error) { normalized := make([]Entry, 0, len(entries)) seen := map[string]bool{} for _, entry := range entries { tag, err := language.Normalize(entry.Tag) if err != nil { return nil, err } if seen[tag] { return nil, ErrDuplicateLanguage } seen[tag] = true social, err := NormalizeSocialLocale(tag, entry.SocialLocale) if err != nil { return nil, err } normalized = append(normalized, Entry{Tag: tag, Enabled: entry.Enabled, SocialLocale: social}) } return normalized, nil } func Plan(current []Row, counts map[string]VersionCount, change Change) ([]Row, error) { entries, err := normalizeEntries(change.Languages) if err != nil { return nil, err } if !slices.ContainsFunc(entries, func(entry Entry) bool { return entry.Enabled }) { return nil, ErrNoEnabledLanguage } defaultTag, err := language.Normalize(change.Default) if err != nil || !slices.ContainsFunc(entries, func(entry Entry) bool { return entry.Tag == defaultTag && entry.Enabled }) { return nil, ErrDefaultNotEnabled } confirmed := map[string]bool{} for _, row := range current { confirmed[row.Tag] = row.Confirmed removed := !slices.ContainsFunc(entries, func(entry Entry) bool { return entry.Tag == row.Tag }) if removed && counts[row.Tag].Total+counts[row.Tag].Trashed > 0 { return nil, ErrLanguageInUse } } next := make([]Row, 0, len(entries)) for _, entry := range entries { isDefault := entry.Tag == defaultTag next = append(next, Row{Tag: entry.Tag, IsDefault: isDefault, Enabled: entry.Enabled, Confirmed: confirmed[entry.Tag] || isDefault, SocialLocale: entry.SocialLocale}) } return next, nil } func EnabledTag(rows []Row, tag string) bool { return slices.ContainsFunc(rows, func(row Row) bool { return row.Tag == tag && row.Enabled }) } func HasRow(rows []Row, tag string) bool { return slices.ContainsFunc(rows, func(row Row) bool { return row.Tag == tag }) } func MissingTags(rows []Row, tags []string) []string { missing := []string{} for _, tag := range tags { if tag != language.Unassigned && !EnabledTag(rows, tag) && !slices.Contains(missing, tag) { missing = append(missing, tag) } } slices.Sort(missing) return missing } func ConfirmedDefaultTag(rows []Row) string { if NeedsSetup(rows) { return language.Unassigned } return DefaultTag(rows) } func SiteTextAllowed(defaultTag string, tag string) bool { return tag == language.Unassigned || tag == defaultTag } func PublicTag(rows []Row, tag string) bool { return tag != language.Unassigned && EnabledTag(rows, tag) } func RequireSingleDefault(rows []Row) error { defaults := 0 for _, row := range rows { if row.IsDefault { defaults++ } } if defaults != 1 { return ErrSingleDefault } return nil } func DefaultTag(rows []Row) string { for _, row := range rows { if row.IsDefault { return row.Tag } } return language.Unassigned } func CompareTags(defaultTag string, left string, right string) int { if left == right { return 0 } if left == defaultTag { return -1 } if right == defaultTag { return 1 } return strings.Compare(left, right) }