feat(i18n): katalog id/en — semua string UI via T()/Tf(), fallback en, TIA_LANG sementara + e2e English
CI / test (push) Successful in 24s

This commit is contained in:
oxpoe committed 2026-10-15 09:47:42 +07:00
1 parent 76cdb0b5bb
commit 19ae0d1c29
13 files changed
+540 -76

No files matched your search

+28 -21
View File
@@ -12,6 +12,7 @@ import (
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/audio"
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/docker"
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/i18n"
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/sysinfo"
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/ui/animation"
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/ui/components"
@@ -133,7 +134,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.loading = false
if msg.err != nil {
m.loadErr = msg.err
m.toast.Show("✖ [ GAGAL ] Docker", msg.err.Error())
m.toast.Show(i18n.T("toast.dockerError"), msg.err.Error())
m.sound.Play(audio.NotifyError)
} else {
m.loadErr = nil
@@ -189,8 +190,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.sound.Play(audio.Click)
return m, tea.Batch(loadContainers(m.cl), loadInfo(m.cl))
case "t": // demo toast (sampai modal asli datang)
m.toast.Show("✔ [ SUKSES ] Konversi Berhasil Disimpan",
"Stack 'web-app' tersimpan di ~/.tia/stacks/web-app/")
m.toast.Show(i18n.T("toast.demoTitle"), i18n.T("toast.demoBody"))
m.sound.Play(audio.NotifySuccess)
case "j", "down":
if len(m.containers) > 0 && m.cursor < len(m.containers)-1 {
@@ -306,6 +306,13 @@ func truncate(s string, w int) string {
return string(r[:w-1]) + "…"
}
// field — label kolom (padding tetap agar nilai sejajar).
func field(label string) string { return fieldW(label, 7) }
func fieldW(label string, w int) string {
return text(fmt.Sprintf("%-*s", w, label))
}
// panel — kotak gaya mirador: judul di border atas (kiri), meta (kanan),
// border redup; konten otomatis dipangkas/padded selebar panel.
func panel(title, meta string, lines []string, w, h int, focused bool) string {
@@ -362,13 +369,13 @@ func (m Model) View() tea.View {
var body, footer string
switch m.view {
case viewHome:
header += dim(" · beranda")
header += dim(" · " + i18n.T("nav.home"))
body = m.homeBody()
footer = m.footerLine("[1/enter] kontainer · [t] toast · [a] reduced-motion · [q] keluar")
footer = m.footerLine(i18n.T("hint.footerHome"))
default:
header += dim(" · kontainer")
header += dim(" · " + i18n.T("nav.containers"))
body = lipgloss.JoinHorizontal(lipgloss.Top, m.sidebarView(), m.contentView())
footer = m.footerLine("[j/k] pilih · [r] muat ulang · [0/esc] beranda · [q] keluar")
footer = m.footerLine(i18n.T("hint.footerContainers"))
}
parts := []string{}
@@ -388,24 +395,24 @@ func (m Model) footerLine(hints string) string {
if m.animating {
anim = fmt.Sprintf("%d", m.fps)
}
return dim(truncate(fmt.Sprintf("%s animasi %s fps · audio: %s", hints, anim, m.sound.Status()), m.width))
return dim(truncate(i18n.Tf("footer.status", hints, anim, m.sound.Status()), m.width))
}
func (m Model) sidebarView() string {
w, h := m.leftWidth(), m.bodyHeight()
title, meta := "Kontainer", fmt.Sprintf("(%d)", len(m.containers))
title, meta := i18n.T("section.containers"), fmt.Sprintf("(%d)", len(m.containers))
if m.loading {
meta = "memuat…"
meta = i18n.T("state.loading")
}
var lines []string
switch {
case m.loadErr != nil:
lines = append(lines, red("GAGAL memuat"), dim("tekan r untuk coba lagi"))
lines = append(lines, red(i18n.T("error.loadFailed")), dim(i18n.T("hint.retry")))
case m.loading:
lines = append(lines, dim("memuat…"))
lines = append(lines, dim(i18n.T("state.loading")))
case len(m.containers) == 0:
lines = append(lines, dim("belum ada kontainer"))
lines = append(lines, dim(i18n.T("state.noContainers")))
default:
rows := h - 2
start := m.scrollStart()
@@ -425,25 +432,25 @@ func (m Model) sidebarView() string {
func (m Model) contentView() string {
w, h := m.width-m.leftWidth(), m.bodyHeight()
title, meta := "Detail", ""
title, meta := i18n.T("section.detail"), ""
var lines []string
switch {
case m.loadErr != nil:
lines = append(lines, red("Gagal memuat daftar kontainer"), "", dim(truncate(m.loadErr.Error(), w-6)))
lines = append(lines, red(i18n.T("error.loadContainers")), "", dim(truncate(m.loadErr.Error(), w-6)))
case len(m.containers) == 0:
lines = append(lines, dim("Belum ada kontainer — jalankan sesuatu dengan Docker, lalu tekan r."))
lines = append(lines, dim(i18n.T("hint.noContainers")))
default:
c := m.containers[min(m.cursor, len(m.containers)-1)]
glyph, color := stateStyle(c.State)
title, meta = c.Name, c.State
lines = append(lines,
text("ID ")+dim(c.ID),
text("Image ")+dim(truncate(c.Image, w-20)),
text("State ")+styled(color, glyph+" "+c.State),
text("Status ")+dim(truncate(c.Status, w-20)),
field(i18n.T("field.id"))+dim(c.ID),
field(i18n.T("field.image"))+dim(truncate(c.Image, w-20)),
field(i18n.T("field.state"))+styled(color, glyph+" "+i18n.TOr("state."+c.State, c.State)),
field(i18n.T("field.status"))+dim(truncate(c.Status, w-20)),
"",
dim("Tab detail (Log · Statistik · Konfigurasi) menyusul di Phase 2."),
dim(i18n.T("hint.tabsComing")),
)
}
return panel(title, meta, lines, w, h, false)