package shadow import ( "strings" "unicode/utf8" ) // Croatian digraphs — must be checked before single letters. // Order matters: longer sequences first. var croatianDigraphs = []struct { src string dst string }{ {"dž", Dzh}, {"Dž", Dzh}, {"DŽ", Dzh}, {"lj", L + Y}, {"Lj", L + Y}, {"LJ", L + Y}, {"nj", N + Y}, {"Nj", N + Y}, {"NJ", N + Y}, } // Croatian single-letter map. Every Croatian letter that differs from // its Shadow equivalent is listed here. Letters that map to themselves // (A→A, B→B, etc.) are handled by the default passthrough. var croatianSingles = map[rune]string{ // Letters that change 'c': T + S, 'C': T + S, // Croatian C = /ts/ 'č': Ch, 'Č': Ch, 'ć': Ch, 'Ć': Ch, // Ć ≈ Č (palatal affricate, close enough) 'đ': Dzh, 'Đ': Dzh, // Đ ≈ Џ (palatal stop, close enough) 'š': Sh, 'Š': Sh, 'ž': Zh, 'Ž': Zh, 'j': Y, 'J': Y, // Passthrough — identical sound, same letter 'a': A, 'A': A, 'b': B, 'B': B, 'd': D, 'D': D, 'e': E, 'E': E, 'f': F, 'F': F, 'g': G, 'G': G, 'h': H, 'H': H, 'i': I, 'I': I, 'k': K, 'K': K, 'l': L, 'L': L, 'm': M, 'M': M, 'n': N, 'N': N, 'o': O, 'O': O, 'p': P, 'P': P, 'r': R, 'R': R, 's': S, 'S': S, 't': T, 'T': T, 'u': U, 'U': U, 'v': V, 'V': V, 'z': Z, 'Z': Z, 'w': W, 'W': W, 'y': Y, 'Y': Y, } // FromCroatian transliterates Croatian (Gaj's Latin) text into the // Shadow Alphabet. Digits and whitespace pass through unchanged. // Unknown characters are preserved as-is. func FromCroatian(text string) string { var out strings.Builder out.Grow(len(text)) i := 0 for i < len(text) { // Try digraphs first (longest match) matched := false for _, dg := range croatianDigraphs { if strings.HasPrefix(text[i:], dg.src) { out.WriteString(dg.dst) i += len(dg.src) matched = true break } } if matched { continue } // Single character r, size := utf8.DecodeRuneInString(text[i:]) if r == utf8.RuneError { i++ continue } if s, ok := croatianSingles[r]; ok { out.WriteString(s) } else { // Digits, spaces, newlines, unknown — passthrough out.WriteRune(r) } i += size } return out.String() }