This builds on @pluiedev's excellent #6004.
## Background: The macOS (and libghostty consumer) Plan
Broadly, the decision I've come to is that for cross-platform
translations (i.e. strings shared across libghostty), we will be using
gettext and libghostty will export helper methods to call those (e.g.
`ghostty_translate` in this PR for singular forms). To be clear, **this
only applies to strings owned by libghostty**. For application-level
strings such as macOS-specific menu items and so on, we still have
choice but will likely using native features.
The reason for this is because converting gettext translations (`po`) to
native formats (Xcode String Catalog, `.strings`/`.stringsdict`) is
nightmare level, in particular for plural forms. I don't see a robust
path to doing it. And if we don't convert and don't use gettext, then
translators would have to maintain an identical translation in multiple
locations. To make matters worse, the macOS translation formats require
Apple-tooling for now unless you want to edit raw JSON.
Leveraging gettext lets us share translations across platforms and take
advantage of proven tech.
## PR Contents
**`pkg/libintl` builds and statically links libintl for macOS.** macOS
doesn't ship libintl with the system while Linux generally does with
libc, so we need to build this ourselves. This makes gettext available
to macOS. libintl is LGPL and we remain in compliance with that despite
static linking because our build process is fully open source, so
downstream consumers can modify our build scripts to replace it if they
wanted to.
~~**`src/os/locale.zig` now sets the `LANGUAGE` environment variable on
macOS based on the app's preferred languages.** macOS lets you configure
the system locale separate from preferred language. We previously relied
solely on `NSLocale.currentLocale`, but this only represents the system
locale. We now also look at `NSLocale.preferredLanguages` (a list in
priority order) and if we support a given language we set `LANGUAGE` so
gettext translates properly. Notably, the above lets us debug
translations in Xcode by setting alternate languages for debug builds
only.~~ Removed this for a future PR since it was problematic.
**`build.zig` unconditionally builds binary `mo` files** since they're
required for all apprts now.
**The macOS app bundles the translation strings.** This includes our
GTK-specific translation strings but the size of these is so small it
isn't worth the complexity of splitting up into multiple `pot`s at this
time, I think.
**i18n APIs moved to `src/os` from `src/apprt/gtk`.** Since these are
now cross-platform/cross-apprt, they're a core API. The only notable
change here is that `_` now maps to `dgettext` and explicitly specifies
our domain so that it's library-friendly. The GTK apprt calls
`initGlobalDomain` so that blueprint translations still work.
## Next Steps
This PR is all groundwork. The macOS app doesn't leverage any of this
yet, although I've verified it all works (e.g. calling the
`ghostty_translate` API from Swift).
For next steps, we need to have a use case for cross-platform
translations and the first one I was looking at was configuration error
messages and other core strings.
AdwAlertDialog is the recommended way to do alert/message dialogs
starting from libadwaita 1.5, and is much easier to manage than
GtkMessageDialog. (The latter is also deprecated since GTK 4.10, but
this PR does not migrate it to use GtkAlertDialog, mostly because of its
obtuse interface and that we'll remove the GtkMessageDialog code anyway
in 1.2 when we remove non-Adwaita builds.)
We also had two bugs where tabs with only one split would display the
"close surface" confirmation dialog, and windows would do the same when
closed via the "Close Window" menu item or by the `close_window` keybind
action. (The "close window" dialog only appears when the user clicks on
the close button on the titlebar.) Initially I was very confused by
this, but it turns out that we don't have any apprt action related to
closing a window, and it was simply closing surfaces...
As of now `gtk4-layer-shell` is unavailable on recent, stable releases
of many distros (Debian 12, Ubuntu 24.04, openSUSE Leap & Tumbleweed,
etc.) and outdated on many others (Nixpkgs 24.11/unstable, Fedora 41,
etc.) This is inconvenient for our users and severely limits where the
quick terminal can be used. As a result we then build gtk4-layer-shell
ourselves by default unless `--system` or `-fsys=gtk4-layer-shell` are
specified. This also allows me to add an idiomatic Zig API on top of the
library and avoiding adding even more raw C code in the GTK apprt.
Since we now build gtk4-layer-shell it should be theoretically available
on all Linux systems we target. As such, the `-Dgtk-layer-shell` build
option has been removed. This is somewhat of an experimental change as I
don't know if gtk4-layer-shell works perfectly across all distros, and
we can always add the option back if need be.
As of now `gtk4-layer-shell` is unavailable on recent, stable releases
of many distros (Debian 12, Ubuntu 24.04, openSUSE Leap & Tumbleweed, etc.)
and outdated on many others (Nixpkgs 24.11/unstable, Fedora 41, etc.)
This is inconvenient for our users and severely limits where the quick
terminal can be used. As a result we then build gtk4-layer-shell ourselves
by default unless `--system` or `-fsys=gtk4-layer-shell` are specified.
This also allows me to add an idiomatic Zig API on top of the library
and avoiding adding even more raw C code in the GTK apprt.
Since we now build gtk4-layer-shell it should be theoretically available
on all Linux systems we target. As such, the `-Dgtk-layer-shell` build
option has been removed. This is somewhat of an experimental change as
I don't know if gtk4-layer-shell works perfectly across all distros, and
we can always add the option back if need be.
For *some* reason we have a binding for close_window but it merely closes
the surface and not the entire window. That is not only misleading but
also just wrong. Now we make a separate apprt action for close_window
that would make it show a close confirmation prompt identical to as if
the user had clicked the (X) button on the window titlebar.
Fixes https://github.com/ghostty-org/ghostty/issues/4947 for gtk
This PR implements the senstive content hiding when displaying the paste
confirmation dialog in secure input mode.
Following changes are implemented:
- in the blueprint for each dialog add a show/hide button that is not
visible by default, and a Revealer that is revealed by default
- save the `secure_input` action value for each surface in the GTK apprt
- pass the value when initializing the paste confirmation dialog
- in the dialog code, alter the visibility of the content and
reveal/hide buttons based on secure input flag value
Demo:
https://github.com/user-attachments/assets/c91cbd3d-ed3b-464d-b4cf-e51fe7aa23b7
I feel like this is already a nearly full implementation, but I'm
leaving this as a draft for now, since i need to look into blueprints
for Adwaita 1.2, and verify if it behaves properly when the dialog is in
not-sensitive input mode and in OSC52 mode.
This is my third (!) attempt at implementing localization support. By
leveraging GTK builder to do most of the `gettext` calls, I can avoid
the whole mess about missing symbols on non-glibc platforms.
Added some documentation too for contributors and translators, just for
good measure.
Supersedes #5214, resolves the GTK half of #2357
When one develops Ghostty while using Ghostty it could lead to an
interesting conundrum: the freshly built Ghostty would use the parent
Ghostty's resources, which would be stale and not reflect any new
changes to resources. This is especially bad for translators, since
their translations would not be reflected in the newly built Ghostty
if they happen to run it under older Ghostty, which is not only
counterintuitive and also painful in terms of workflow.
Now, on debug builds we always try to use the terminfo detection method
first in order to locate the zig-out/share/ghostty folder, and only fall
back to GHOSTTY_RESOURCES_DIR if the executable is for some reason no
longer in zig-out. You can test this behavior by manually moving the
Ghostty executable out of zig-out, and then launching it with and without
Ghostty.