From 570c80da225e31c9b82d43a4bbca1e00aeb3b8d6 Mon Sep 17 00:00:00 2001 From: Piero-93 Date: Sun, 14 Jun 2026 14:30:29 +0200 Subject: [PATCH] Localize all of nirilayout's interface text and let users control casing and input box alignment. - i18n via gettext .po/.mo (leonelquinteros/gotext), catalogs embedded with go:embed. English is the source language; Italian (it) added. - Language selection precedence: -lang flag > OS locale (LC_ALL/LC_MESSAGES/LANG) > English fallback. Locales like it_IT.UTF-8 are matched to their base language. - Interface strings now use standard sentence casing; the new -lowercase flag forces all of nirilayout's own text to lowercase. User-defined layout and output names are never altered. - New -leftalign flag left-aligns the search box text (default centered). - Makefile targets (pot/update-po/i18n) to maintain translations; compiled .mo files are committed so building needs no gettext tooling. - Updated README.md --- Makefile | 37 +++++- README.md | 91 ++++++++++++- cmd/nirilayout/main.go | 19 ++- config.go | 8 +- draw.go | 5 +- go.mod | 1 + go.sum | 2 + i18n.go | 190 +++++++++++++++++++++++++++ i18n_test.go | 75 +++++++++++ locales/it/LC_MESSAGES/nirilayout.mo | Bin 0 -> 1879 bytes locales/it/LC_MESSAGES/nirilayout.po | 82 ++++++++++++ locales/nirilayout.pot | 71 ++++++++++ nirilayout.go | 16 ++- 13 files changed, 580 insertions(+), 17 deletions(-) create mode 100644 i18n.go create mode 100644 i18n_test.go create mode 100644 locales/it/LC_MESSAGES/nirilayout.mo create mode 100644 locales/it/LC_MESSAGES/nirilayout.po create mode 100644 locales/nirilayout.pot diff --git a/Makefile b/Makefile index 9280982..38d84b4 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,45 @@ MAIN := ./cmd/nirilayout SRCS := $(wildcard *.go cmd/nirilayout/*.go) go.mod go.sum style.css -nirilayout: $(SRCS) +# Localization (gettext). Source files to scan for translatable T("...") calls. +I18N_SRCS := $(wildcard *.go cmd/nirilayout/*.go) +POT := locales/nirilayout.pot +POFILES := $(wildcard locales/*/LC_MESSAGES/*.po) +MOFILES := $(POFILES:.po=.mo) + +nirilayout: $(SRCS) $(MOFILES) go build -o $@ $(MAIN) -nirilayout-profile: $(SRCS) +nirilayout-profile: $(SRCS) $(MOFILES) go build -o $@ -tags profile $(MAIN) -install: $(SRCS) +install: $(SRCS) $(MOFILES) go install $(MAIN) +# Regenerate the .pot template from the Go sources. +pot: $(POT) +$(POT): $(I18N_SRCS) + xgettext --language=C --from-code=UTF-8 --keyword=T:1 --keyword=Tf:1 --keyword=N:1 \ + --package-name=nirilayout \ + --msgid-bugs-address="https://github.com/calico32/nirilayout/issues" \ + --copyright-holder="nirilayout contributors" \ + -o $@ $(I18N_SRCS) + +# Merge the latest .pot into every existing .po, preserving translations. +update-po: $(POT) + @for po in $(POFILES); do \ + echo "msgmerge $$po"; \ + msgmerge --update --backup=none $$po $(POT); \ + done + +# Compile .po files to .mo. Build targets depend on these. +%.mo: %.po + msgfmt --check -o $@ $< + +# Regenerate translations: refresh .po files from sources, then recompile .mo. +i18n: update-po $(MOFILES) + clean: rm -f nirilayout nirilayout-profile -.PHONY: clean +.PHONY: clean pot update-po i18n diff --git a/README.md b/README.md index 3013c67..41ef005 100644 --- a/README.md +++ b/README.md @@ -188,10 +188,99 @@ prefixes of each other. Layouts are presented in lexicographical order by name. If you want to change the order, you can rename the files in the config directory. +## Command-line options + +| Option | Description | +| ------------- | -------------------------------------------------------------------------------------------- | +| `-c ` | niri config directory (default `~/.config/niri`). | +| `-lang `| Interface language code, e.g. `it`. Overrides the system locale. See [Localization](#localization). | +| `-lowercase` | Render all of nirilayout's own interface text in lowercase. See [Casing](#casing). | +| `-leftalign` | Left-align the text in the search box. See [Search box alignment](#search-box-alignment). | + +Run `nirilayout -h` for the full list. + +## Appearance + +### Casing + +By default nirilayout uses standard sentence casing for its own interface text +(for example `Esc to quit`). If you prefer everything in lowercase, pass the +`-lowercase` flag: + +```sh +nirilayout -lowercase +``` + +This only affects nirilayout's own interface strings — the names of your layouts +and outputs are always shown exactly as you wrote them. + +### Search box alignment + +The text you type into the search box is centered by default. Pass the +`-leftalign` flag to left-align it instead: + +```sh +nirilayout -leftalign +``` + +## Localization + +nirilayout's interface can be translated. The language is chosen automatically, +with the following precedence: + +1. The `-lang` flag, when set, wins over everything (e.g. `nirilayout -lang it`). +2. Otherwise the operating system locale is used, read from the `LC_ALL`, + `LC_MESSAGES`, and `LANG` environment variables (in that order). A locale + like `it_IT.UTF-8` is matched to its base language `it`. +3. Otherwise English is used. + +If the selected language has no translation, nirilayout falls back to English. + +Currently available languages: + +- English (`en`, source language) +- Italian (`it`) + +### Adding a translation + +Translations use [gettext](https://www.gnu.org/software/gettext/). The compiled +`.mo` catalogs are committed to the repository and embedded into the binary, so +**building or running nirilayout never requires any gettext tooling**. You only +need the gettext tools (`xgettext`, `msgmerge`, `msgfmt`) to *edit* translations. + +To add a new language (using French, `fr`, as an example): + +1. Refresh the message template and existing translations from the source: + + ```sh + make update-po + ``` + +2. Create the catalog directory and initialize the `.po` file from the template: + + ```sh + mkdir -p locales/fr/LC_MESSAGES + msginit -i locales/nirilayout.pot -o locales/fr/LC_MESSAGES/nirilayout.po -l fr + ``` + +3. Translate the `msgstr` entries in `locales/fr/LC_MESSAGES/nirilayout.po`. + +4. Compile the catalogs (this produces the committed `.mo` files): + + ```sh + make i18n + ``` + +5. Add the new language to the list above and open a pull request. + +To update an existing translation, edit its `.po` file, run `make i18n`, and +commit both the `.po` and the regenerated `.mo`. + # Contributing Contributions are welcome! If you find a bug or have a feature request, please -open an issue or a pull request. +open an issue or a pull request. Translations are especially welcome — see +[Adding a translation](#adding-a-translation). # License diff --git a/cmd/nirilayout/main.go b/cmd/nirilayout/main.go index 8fb4de1..79a69f6 100644 --- a/cmd/nirilayout/main.go +++ b/cmd/nirilayout/main.go @@ -15,15 +15,28 @@ import ( ) func main() { + // Initialize i18n from the system locale first so that flag.Usage (which + // flag.Parse may invoke on -h or a parse error) is already localized. It + // is re-initialized after flag.Parse to honor -lang and -lowercase. + nirilayout.InitI18n() + flag.Usage = func() { - fmt.Printf("nirilayout is a layout switcher for niri.\n\nCommand-line options:\n") + fmt.Print(nirilayout.T("nirilayout is a layout switcher for niri.\n\nCommand-line options:\n")) + // Translate each flag's usage string in place, then let the flag + // package handle the formatting (types, defaults, alignment). + flag.VisitAll(func(f *flag.Flag) { + f.Usage = nirilayout.T(f.Usage) + }) flag.PrintDefaults() - fmt.Printf("\nTo use nirilayout, create layouts in files called ~/.config/niri/layout_.kdl and run nirilayout.\nSee the README for more details:\n") - fmt.Printf(" https://github.com/calico32/nirilayout/blob/main/README.md\n") + fmt.Print(nirilayout.T("\nTo use nirilayout, create layouts in files called ~/.config/niri/layout_.kdl and run nirilayout.\nSee the README for more details:\n")) + fmt.Print(" https://github.com/calico32/nirilayout/blob/main/README.md\n") } flag.Parse() + // Apply -lang and -lowercase now that flags are parsed. + nirilayout.InitI18n() + var layouts []nirilayout.Layout var current string diff --git a/config.go b/config.go index f43857f..08a30cb 100644 --- a/config.go +++ b/config.go @@ -26,7 +26,13 @@ const ( defaultLineSpacing = 4 ) -var niriConfigDir = flag.String("c", "~/.config/niri", "niri config directory") +var niriConfigDir = flag.String("c", "~/.config/niri", N("niri config directory")) + +var ( + langFlag = flag.String("lang", "", N("interface language code (e.g. \"it\"); overrides the system locale")) + lowercaseFlag = flag.Bool("lowercase", false, N("render all interface text in lowercase")) + leftAlignFlag = flag.Bool("leftalign", false, N("left-align the text in the search box (default is centered)")) +) func GetNiriConfigDir() (configDir string, err error) { if strings.HasPrefix(*niriConfigDir, "~") { diff --git a/draw.go b/draw.go index 4342f50..12c9216 100644 --- a/draw.go +++ b/draw.go @@ -123,10 +123,11 @@ func drawLayout(layout Layout) *gtk.DrawingArea { cr.MoveTo(0, 0) if len(outputs) == 0 { - extents := cr.TextExtents("no preview available") + msg := T("No preview available") + extents := cr.TextExtents(msg) cr.MoveTo(float64(width)/2-extents.Width/2-extents.XBearing, float64(height)/2-extents.Height/2-extents.YBearing) cr.SetSourceRGBA(rgba(gray400)) - cr.ShowText("no preview available") + cr.ShowText(msg) return } diff --git a/go.mod b/go.mod index ec36b14..fa69781 100644 --- a/go.mod +++ b/go.mod @@ -17,4 +17,5 @@ require ( github.com/diamondburned/gotk4-layer-shell/pkg v0.0.0-20240109211357-6efa9f6dc438 github.com/diamondburned/gotk4/pkg v0.3.1 github.com/google/go-cmp v0.7.0 + github.com/leonelquinteros/gotext v1.7.2 ) diff --git a/go.sum b/go.sum index 320a040..0abdbe4 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/diamondburned/gotk4/pkg v0.3.1 h1:uhkXSUPUsCyz3yujdvl7DSN8jiLS2BgNTQE github.com/diamondburned/gotk4/pkg v0.3.1/go.mod h1:DqeOW+MxSZFg9OO+esk4JgQk0TiUJJUBfMltKhG+ub4= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+YP7G1Mc= +github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8= go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6 h1:lGdhQUN/cnWdSH3291CUuxSEqc+AsGTiDxPP3r2J0l4= go4.org/unsafe/assume-no-moving-gc v0.0.0-20231121144256-b99613f794b6/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= diff --git a/i18n.go b/i18n.go new file mode 100644 index 0000000..4267446 --- /dev/null +++ b/i18n.go @@ -0,0 +1,190 @@ +package nirilayout + +import ( + "embed" + "fmt" + "os" + "path" + "strings" + + "github.com/leonelquinteros/gotext" +) + +// Compiled message catalogs, one per language, laid out in the standard +// gettext directory structure: locales//LC_MESSAGES/nirilayout.mo. +// English is the source language (the msgids themselves), so it has no +// catalog and is never loaded here. +// +//go:embed locales +var localesFS embed.FS + +const i18nDomain = "nirilayout" + +var ( + // catalog maps source (English) msgids to their translation in the active + // language. It is nil when the active language is English or no catalog + // could be loaded, in which case the source msgids are used verbatim. + catalog map[string]string + // lowercaseEnabled forces all translated interface text to lowercase. + lowercaseEnabled bool +) + +// InitI18n selects the interface language and lowercase preference. It must be +// called after flag.Parse. Language selection follows this precedence: +// +// 1. the -lang flag, when set, overrides everything; +// 2. otherwise the operating system locale, read from LC_ALL, then +// LC_MESSAGES, then LANG; +// 3. otherwise English. +// +// If the selected language has no compiled catalog, the source (English) +// msgids are used as a fallback. +func InitI18n() { + lang := *langFlag + lowercase := *lowercaseFlag + + // The flags may not be parsed yet — InitI18n is also called before + // flag.Parse so that flag.Usage (triggered by -h during parsing) is + // already localized. In that case, fall back to scanning the raw + // arguments so that "-lang"/"-lowercase" still take effect. + if lang == "" || !lowercase { + argLang, argLower := scanI18nArgs(os.Args[1:]) + if lang == "" { + lang = argLang + } + lowercase = lowercase || argLower + } + + lowercaseEnabled = lowercase + + if lang == "" { + lang = detectSystemLang() + } + lang = normalizeLang(lang) + + if lang == "en" { + catalog = nil + return + } + + data, err := localesFS.ReadFile(path.Join("locales", lang, "LC_MESSAGES", i18nDomain+".mo")) + if err != nil { + catalog = nil + return + } + + catalog = parseCatalog(data) +} + +// parseCatalog parses a compiled gettext .mo file and returns a map from source +// msgid to its translation. Empty or identity translations are omitted so that +// lookups fall back to the source msgid. +func parseCatalog(data []byte) map[string]string { + mo := gotext.NewMo() + mo.Parse(data) + + m := make(map[string]string) + for id, tr := range mo.GetDomain().GetTranslations() { + if s := tr.Get(); s != "" && s != id { + m[id] = s + } + } + return m +} + +// lookup returns the translation for msgid, or msgid itself when there is no +// active catalog or no entry for it. +func lookup(msgid string) string { + if catalog != nil { + if s, ok := catalog[msgid]; ok { + return s + } + } + return msgid +} + +// scanI18nArgs extracts the -lang value and the -lowercase presence directly +// from the raw command-line arguments. It is used to localize flag.Usage, +// which the flag package may invoke (on -h) before the flags are parsed. +func scanI18nArgs(args []string) (lang string, lowercase bool) { + for i := 0; i < len(args); i++ { + a := args[i] + switch { + case a == "--": + return lang, lowercase + case a == "-lang" || a == "--lang": + if i+1 < len(args) { + lang = args[i+1] + i++ + } + case strings.HasPrefix(a, "-lang="): + lang = strings.TrimPrefix(a, "-lang=") + case strings.HasPrefix(a, "--lang="): + lang = strings.TrimPrefix(a, "--lang=") + case a == "-lowercase" || a == "--lowercase", + a == "-lowercase=true" || a == "--lowercase=true": + lowercase = true + } + } + return lang, lowercase +} + +// detectSystemLang reads the OS locale from the standard POSIX environment +// variables, in order of precedence. Returns "" if none are set. +func detectSystemLang() string { + for _, env := range []string{"LC_ALL", "LC_MESSAGES", "LANG"} { + if v := os.Getenv(env); v != "" { + return v + } + } + return "" +} + +// normalizeLang reduces a raw locale string such as "it_IT.UTF-8@euro" to its +// base language code ("it"). The "C" and "POSIX" locales, as well as an empty +// value, map to English. +func normalizeLang(raw string) string { + s := raw + if i := strings.IndexAny(s, ".@"); i >= 0 { + s = s[:i] + } + if i := strings.IndexAny(s, "_-"); i >= 0 { + s = s[:i] + } + s = strings.ToLower(strings.TrimSpace(s)) + if s == "" || s == "c" || s == "posix" { + return "en" + } + return s +} + +// N marks a string for translation extraction without translating it. It is a +// no-op at runtime, returning its argument unchanged. Use it where the string +// must be a plain literal at the point of definition (e.g. flag usage strings +// registered before InitI18n runs) but should still be picked up by xgettext. +// The recorded msgid can then be translated later with T. +func N(s string) string { return s } + +// T translates msgid into the active language. When -lowercase is active the +// result is lowercased. Untranslated msgids fall back to English. The msgid may +// be a non-constant string (e.g. a flag usage string looked up at runtime); for +// translating a format string with arguments, use Tf. +func T(msgid string) string { + s := lookup(msgid) + if lowercaseEnabled { + s = strings.ToLower(s) + } + return s +} + +// Tf translates format into the active language and then formats it with the +// given arguments using fmt.Sprintf semantics. When -lowercase is active the +// final (formatted) string is lowercased. Untranslated formats fall back to +// English. +func Tf(format string, args ...any) string { + s := fmt.Sprintf(lookup(format), args...) + if lowercaseEnabled { + s = strings.ToLower(s) + } + return s +} diff --git a/i18n_test.go b/i18n_test.go new file mode 100644 index 0000000..853eed8 --- /dev/null +++ b/i18n_test.go @@ -0,0 +1,75 @@ +package nirilayout + +import "testing" + +func TestNormalizeLang(t *testing.T) { + cases := map[string]string{ + "it": "it", + "it_IT": "it", + "it_IT.UTF-8": "it", + "it_IT.UTF-8@eu": "it", + "en_US.UTF-8": "en", + "pt-BR": "pt", + "C": "en", + "POSIX": "en", + "": "en", + " IT ": "it", + } + for in, want := range cases { + if got := normalizeLang(in); got != want { + t.Errorf("normalizeLang(%q) = %q, want %q", in, got, want) + } + } +} + +func TestTFallbackToMsgid(t *testing.T) { + defer restoreI18n(catalog, lowercaseEnabled) + catalog = nil + lowercaseEnabled = false + + if got := T("Esc to quit"); got != "Esc to quit" { + t.Errorf("T fallback = %q, want %q", got, "Esc to quit") + } + if got := Tf("Error loading layouts: %v", "boom"); got != "Error loading layouts: boom" { + t.Errorf("Tf fallback with args = %q, want %q", got, "Error loading layouts: boom") + } +} + +func TestTLowercase(t *testing.T) { + defer restoreI18n(catalog, lowercaseEnabled) + catalog = nil + lowercaseEnabled = true + + if got := T("Name or shortcut…"); got != "name or shortcut…" { + t.Errorf("T lowercase = %q, want %q", got, "name or shortcut…") + } + if got := Tf("Error loading layouts: %v", "BOOM"); got != "error loading layouts: boom" { + t.Errorf("Tf lowercase with args = %q, want %q", got, "error loading layouts: boom") + } +} + +func TestTItalianCatalog(t *testing.T) { + defer restoreI18n(catalog, lowercaseEnabled) + lowercaseEnabled = false + + data, err := localesFS.ReadFile("locales/it/LC_MESSAGES/nirilayout.mo") + if err != nil { + t.Fatalf("embedded Italian catalog missing: %v", err) + } + catalog = parseCatalog(data) + + if got := T("Esc to quit"); got != "Esc per uscire" { + t.Errorf("T(it) = %q, want %q", got, "Esc per uscire") + } + // An unknown msgid must fall back to the source string. Use a variable so + // xgettext does not extract this test-only string into the catalog. + unknown := "Totally untranslated string" + if got := T(unknown); got != unknown { + t.Errorf("T(it) unknown = %q, want fallback to source", got) + } +} + +func restoreI18n(c map[string]string, lower bool) { + catalog = c + lowercaseEnabled = lower +} diff --git a/locales/it/LC_MESSAGES/nirilayout.mo b/locales/it/LC_MESSAGES/nirilayout.mo new file mode 100644 index 0000000000000000000000000000000000000000..4d38dd0d01bed6e1d4d36e9f65820d295ef7b2db GIT binary patch literal 1879 zcmZuxTZt@s!FVu9O=_#$Q>Qq(l zW`jsx{Q-*p0TF!CzaahqL3|P)1Ydmj-LJZ5W@i%%im$7>>Ri5azWVcpQ(p;;XE9&F z{2cRn%s(*WphtxG5_ksqGVllBGr(Vfr-8o$KLq{_d=Yr%Q6b(2egND8egk|7_#5yt z@E_o7zzdHF@e%Ia27Zh6kEet<4}9lwA)W)?0mggxfgc0E2EGdX7x*sl^bpM*=L0&X$o?US+SWVx0xx- znD$4PukDU#Y8_S9F%=x7(tenTk#pG8Ru;;X3+F>Re;`Iar(o$$qk^~zFNAY`W?jge z@YntC#7(<64!eyhGTPHDk<^JqRwL~4L1HI!iC#{UkwxRR;{(NqBo7d(oM;xx1a?!I zN0Ln0$P&J6!L-I%nbBDl&aQ8fJz(cl0peof{>TTekRMoNp?Mlosa0v(pMmd&$a8y; zrORhDv3F^$;Hhjhet~?BKDk&Ik#)F>6`(>n&VzMF?QU0!cv{XGA%CbsK4aH19&ctz za@AH9YLseaz-1kjHL(DWO@V_bGD2D8w}?#JLv}ex6?@Kp3ZLmtk-pE)^^iZl(L6JlamL zp%sTT=npQY{fp_wCJi=*{r(&MLBF5ekUpeiCymzLYJ?D(=+#;(vqkw#I?rMIz47(*(($<%$&_6> zGUx_$@sKV}V0lkBPU`f!b(Keqb-VQ2gDvVlx7Q4v*`|&4twbbyC_&>Tr1796p%t|# z>s*rvM9E49^*|&4k0>Er_FOh<#cmcu_h>LlN-v~YBY;YlrLt%<$TP@NtA#!h6>B^M zPUR4yfnJV-T52^~Xnc;=gnFV}$>-MP3N=!)6)dC9W|2#9_5i;!x{7zFwHM_%|GTQ% zhxOWdI!P8J+nSenhXU75RWPc2ZH=11u85_H!bN?W=uYd`7wwJ^Az{B%@GQzG%0K6V z%{@urYOY|IB(I{RrGR3fB>2OQ!JcdB4D3M<=z@tToQSvt=Aw{j4Q3&4TnNxdRJQ{O z>td.kdl files in ~/.config/niri to " +"use nirilayout." +msgstr "" +"Nessun layout trovato. Crea dei file layout_.kdl in ~/.config/niri per " +"usare nirilayout." + +#: cmd/nirilayout/main.go:24 +msgid "" +"nirilayout is a layout switcher for niri.\n" +"\n" +"Command-line options:\n" +msgstr "" +"nirilayout è un selettore di layout per niri.\n" +"\n" +"Opzioni da riga di comando:\n" + +#: cmd/nirilayout/main.go:31 +msgid "" +"\n" +"To use nirilayout, create layouts in files called ~/.config/niri/" +"layout_.kdl and run nirilayout.\n" +"See the README for more details:\n" +msgstr "" +"\n" +"Per usare nirilayout, crea dei layout in file chiamati ~/.config/niri/" +"layout_.kdl ed esegui nirilayout.\n" +"Consulta il README per maggiori dettagli:\n" diff --git a/locales/nirilayout.pot b/locales/nirilayout.pot new file mode 100644 index 0000000..ea4f978 --- /dev/null +++ b/locales/nirilayout.pot @@ -0,0 +1,71 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR nirilayout contributors +# This file is distributed under the same license as the nirilayout package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: nirilayout\n" +"Report-Msgid-Bugs-To: https://github.com/calico32/nirilayout/issues\n" +"POT-Creation-Date: 2026-06-14 07:39+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: config.go:29 +msgid "niri config directory" +msgstr "" + +#: config.go:32 +msgid "interface language code (e.g. \"it\"); overrides the system locale" +msgstr "" + +#: config.go:33 +msgid "render all interface text in lowercase" +msgstr "" + +#: config.go:34 +msgid "left-align the text in the search box (default is centered)" +msgstr "" + +#: draw.go:126 +msgid "No preview available" +msgstr "" + +#: i18n_test.go:34 i18n_test.go:67 nirilayout.go:159 +msgid "Esc to quit" +msgstr "" + +#: i18n_test.go:37 i18n_test.go:50 nirilayout.go:80 +msgid "Error loading layouts: %v" +msgstr "" + +#: i18n_test.go:47 nirilayout.go:139 +msgid "Name or shortcut…" +msgstr "" + +#: nirilayout.go:84 +msgid "" +"No layouts found. Please create layout_.kdl files in ~/.config/niri to " +"use nirilayout." +msgstr "" + +#: cmd/nirilayout/main.go:24 +msgid "" +"nirilayout is a layout switcher for niri.\n" +"\n" +"Command-line options:\n" +msgstr "" + +#: cmd/nirilayout/main.go:31 +msgid "" +"\n" +"To use nirilayout, create layouts in files called ~/.config/niri/" +"layout_.kdl and run nirilayout.\n" +"See the README for more details:\n" +msgstr "" diff --git a/nirilayout.go b/nirilayout.go index c7a2797..edb1280 100644 --- a/nirilayout.go +++ b/nirilayout.go @@ -14,7 +14,7 @@ import ( "github.com/diamondburned/gotk4/pkg/gtk/v4" ) -const version = `nirilayout v0.3.0` +const version = `nirilayout v0.4.0` //go:embed style.css var appStylesheet string @@ -77,11 +77,11 @@ func Run(app *gtk.Application, layouts []Layout, startIndex int, err error) { var selector *gtk.FlowBox if err != nil { - label := gtk.NewLabel(fmt.Sprintf("Error loading layouts: %v", err)) + label := gtk.NewLabel(Tf("Error loading layouts: %v", err)) label.SetHAlign(gtk.AlignCenter) root.Append(label) } else if len(layouts) == 0 { - label := gtk.NewLabel("No layouts found. Please create layout_.kdl files ~/.config/niri to use nirilayout.") + label := gtk.NewLabel(T("No layouts found. Please create layout_.kdl files in ~/.config/niri to use nirilayout.")) label.SetHAlign(gtk.AlignCenter) root.Append(label) } else { @@ -131,8 +131,12 @@ func Run(app *gtk.Application, layouts []Layout, startIndex int, err error) { input := gtk.NewEntry() input.SetSizeRequest(400, 0) - input.SetAlignment(0.5) - input.SetPlaceholderText("name or shortcut...") + if *leftAlignFlag { + input.SetAlignment(0) + } else { + input.SetAlignment(0.5) + } + input.SetPlaceholderText(T("Name or shortcut…")) input.ConnectChanged(func() { text := input.Text() for _, layout := range layouts { @@ -152,7 +156,7 @@ func Run(app *gtk.Application, layouts []Layout, startIndex int, err error) { label.SetSensitive(false) label.SetMarginEnd(16) inputBox.SetStartWidget(label) - label = gtk.NewLabel("esc to quit") + label = gtk.NewLabel(T("Esc to quit")) label.SetSensitive(false) label.SetMarginStart(16) inputBox.SetEndWidget(label)