f7442c3aea
Formatting only: import order, and statements that were packed onto one line split out. `git diff -w` shows nothing but that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ
64 lines
1.9 KiB
Go
64 lines
1.9 KiB
Go
// Package ttsnorm rewrites machine-formatted dates/times/numbers into RU text
|
|
// a TTS voice speaks naturally — so "10.07.2026" is not read as "number dot
|
|
// number dot number". Pure, deterministic; runs on reply/nudge text before synth.
|
|
package ttsnorm
|
|
|
|
import (
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var months = [...]string{"", "января", "февраля", "марта", "апреля", "мая",
|
|
"июня", "июля", "августа", "сентября", "октября", "ноября", "декабря"}
|
|
|
|
var (
|
|
reDateY = regexp.MustCompile(`\b(\d{1,2})\.(\d{1,2})\.(\d{4})\b`)
|
|
reDate = regexp.MustCompile(`\b(\d{1,2})\.(\d{1,2})\b`)
|
|
reTime = regexp.MustCompile(`\b(\d{1,2}):(\d{2})\b`)
|
|
reDots = regexp.MustCompile(`\b\d+(?:\.\d+){2,}\b`)
|
|
)
|
|
|
|
// Speakable rewrites d.m.y, d.m, h:mm, and residual dotted-number runs.
|
|
// Order matters: dates with year first, then times, then multi-dot numbers
|
|
// (3+ parts — never valid dates), then 2-part dates.
|
|
func Speakable(s string) string {
|
|
s = reDateY.ReplaceAllStringFunc(s, func(m string) string {
|
|
p := reDateY.FindStringSubmatch(m)
|
|
return spokenDate(p[1], p[2], p[3])
|
|
})
|
|
s = reTime.ReplaceAllStringFunc(s, func(m string) string {
|
|
p := reTime.FindStringSubmatch(m)
|
|
return p[1] + " часов " + p[2] + " минут"
|
|
})
|
|
s = reDots.ReplaceAllStringFunc(s, func(m string) string {
|
|
return strings.Join(strings.Split(m, "."), " точка ")
|
|
})
|
|
s = reDate.ReplaceAllStringFunc(s, func(m string) string {
|
|
p := reDate.FindStringSubmatch(m)
|
|
return spokenDate(p[1], p[2], "")
|
|
})
|
|
return s
|
|
}
|
|
|
|
func spokenDate(dd, mm, yyyy string) string {
|
|
mi, _ := strconv.Atoi(mm)
|
|
if mi < 1 || mi > 12 {
|
|
return dd + " " + mm + gap(yyyy)
|
|
}
|
|
day := strconv.Itoa(mustInt(dd))
|
|
out := day + " " + months[mi]
|
|
if yyyy != "" {
|
|
out += " " + yyyy
|
|
}
|
|
return out
|
|
}
|
|
|
|
func mustInt(s string) int { n, _ := strconv.Atoi(s); return n }
|
|
func gap(y string) string {
|
|
if y == "" {
|
|
return ""
|
|
}
|
|
return " " + y
|
|
}
|