-
Notifications
You must be signed in to change notification settings - Fork 704
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Groundwork for cross-platform i18n with libintl for libghostty/macOS (#…
…6619) 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.
- Loading branch information
Showing
26 changed files
with
3,863 additions
and
84 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
//! Provides libintl for macOS. | ||
//! | ||
//! IMPORTANT: This is only for macOS. We could support other platforms | ||
//! if/when we need to but generally Linux provides libintl in libc. | ||
//! Windows we'll have to figure out when we get there. | ||
//! | ||
//! Since this is only for macOS, there's a lot of hardcoded stuff | ||
//! here that assumes macOS. For example, I generated the config.h | ||
//! on my own machine (a Mac) and then copied it here. This isn't | ||
//! ideal since we should do the same detection that gettext's configure | ||
//! script does, but its quite a bit of work to do that. | ||
//! | ||
//! UPGRADING: If you need to upgrade gettext, then the only thing to | ||
//! really watch out for is the xlocale.h include we added manually | ||
//! at the end of config.h. The comment there notes why. When we upgrade | ||
//! we should audit our config.h and make sure we add that back (if we | ||
//! have to). | ||
|
||
const std = @import("std"); | ||
|
||
pub fn build(b: *std.Build) !void { | ||
const target = b.standardTargetOptions(.{}); | ||
const optimize = b.standardOptimizeOption(.{}); | ||
|
||
const upstream = b.dependency("gettext", .{}); | ||
|
||
var flags = std.ArrayList([]const u8).init(b.allocator); | ||
defer flags.deinit(); | ||
try flags.appendSlice(&.{ | ||
"-DHAVE_CONFIG_H", | ||
"-DLOCALEDIR=\"\"", | ||
}); | ||
|
||
{ | ||
const lib = b.addStaticLibrary(.{ | ||
.name = "intl", | ||
.target = target, | ||
.optimize = optimize, | ||
}); | ||
lib.linkLibC(); | ||
lib.addIncludePath(b.path("")); | ||
lib.addIncludePath(upstream.path("gettext-runtime/intl")); | ||
lib.addIncludePath(upstream.path("gettext-runtime/intl/gnulib-lib")); | ||
|
||
if (target.result.isDarwin()) { | ||
const apple_sdk = @import("apple_sdk"); | ||
try apple_sdk.addPaths(b, &lib.root_module); | ||
} | ||
|
||
lib.addCSourceFiles(.{ | ||
.root = upstream.path("gettext-runtime/intl"), | ||
.files = srcs, | ||
.flags = flags.items, | ||
}); | ||
|
||
lib.installHeader(b.path("libintl.h"), "libintl.h"); | ||
b.installArtifact(lib); | ||
} | ||
} | ||
|
||
const srcs: []const []const u8 = &.{ | ||
"bindtextdom.c", | ||
"dcgettext.c", | ||
"dcigettext.c", | ||
"dcngettext.c", | ||
"dgettext.c", | ||
"dngettext.c", | ||
"explodename.c", | ||
"finddomain.c", | ||
"gettext.c", | ||
"hash-string.c", | ||
"intl-compat.c", | ||
"l10nflist.c", | ||
"langprefs.c", | ||
"loadmsgcat.c", | ||
"localealias.c", | ||
"log.c", | ||
"ngettext.c", | ||
"plural-exp.c", | ||
"plural.c", | ||
"setlocale.c", | ||
"textdomain.c", | ||
"version.c", | ||
"compat.c", | ||
|
||
// There's probably a better way to detect that we need these, but | ||
// these are hardcoded for now for macOS. | ||
"gnulib-lib/getlocalename_l-unsafe.c", | ||
"gnulib-lib/localename.c", | ||
"gnulib-lib/localename-environ.c", | ||
"gnulib-lib/localename-unsafe.c", | ||
"gnulib-lib/setlocale-lock.c", | ||
"gnulib-lib/setlocale_null.c", | ||
"gnulib-lib/setlocale_null-unlocked.c", | ||
|
||
// Not needed for macOS, but we might need them for other platforms. | ||
// If we expand this to support other platforms, we should uncomment | ||
// these. | ||
// "osdep.c", | ||
// "printf.c", | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
.{ | ||
.name = "libintl", | ||
.version = "0.24.0", | ||
.paths = .{""}, | ||
.dependencies = .{ | ||
.gettext = .{ | ||
.url = "https://deps.files.ghostty.org/gettext-0.24.tar.gz", | ||
.hash = "1220f870c853529233ea64a108acaaa81f8d06d7ff4b66c76930be7d78d508aff7a2", | ||
}, | ||
|
||
.apple_sdk = .{ .path = "../apple-sdk" }, | ||
}, | ||
} |
Oops, something went wrong.