feat(i18n): katalog id/en — semua string UI via T()/Tf(), fallback en, TIA_LANG sementara + e2e English
CI / test (push) Successful in 24s
CI / test (push) Successful in 24s
This commit is contained in:
1 parent
76cdb0b5bb
commit
19ae0d1c29
13 files changed
+540
-76
No files matched your search
+28
-21
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user