Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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
91 changes: 90 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <dir>` | niri config directory (default `~/.config/niri`). |
| `-lang <code>`| 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

Expand Down
19 changes: 16 additions & 3 deletions cmd/nirilayout/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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_<name>.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_<name>.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

Expand Down
8 changes: 7 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, "~") {
Expand Down
5 changes: 3 additions & 2 deletions draw.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
Loading