mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-05-19 11:31:19 +00:00
gtk: add localization support, take 3 (#6004)
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
This commit is contained in:
@@ -39,6 +39,7 @@ const ConfigErrorsWindow = @import("ConfigErrorsWindow.zig");
|
||||
const ClipboardConfirmationWindow = @import("ClipboardConfirmationWindow.zig");
|
||||
const Split = @import("Split.zig");
|
||||
const c = @import("c.zig").c;
|
||||
const i18n = @import("i18n.zig");
|
||||
const version = @import("version.zig");
|
||||
const inspector = @import("inspector.zig");
|
||||
const key = @import("key.zig");
|
||||
@@ -98,6 +99,11 @@ quit_timer: union(enum) {
|
||||
pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||
_ = opts;
|
||||
|
||||
// This can technically be placed *anywhere* because we don't have any
|
||||
// localized log messages. It just has to be placed before any localized
|
||||
// widgets are drawn.
|
||||
try i18n.init(core_app.alloc);
|
||||
|
||||
// Log our GTK version
|
||||
log.info("GTK version build={d}.{d}.{d} runtime={d}.{d}.{d}", .{
|
||||
c.GTK_MAJOR_VERSION,
|
||||
|
||||
@@ -35,6 +35,7 @@ const gtk_key = @import("key.zig");
|
||||
const c = @import("c.zig").c;
|
||||
const Builder = @import("Builder.zig");
|
||||
const adwaita = @import("adwaita.zig");
|
||||
const i18n = @import("i18n.zig");
|
||||
|
||||
const log = std.log.scoped(.gtk_surface);
|
||||
|
||||
@@ -1152,7 +1153,7 @@ pub fn setClipboardString(
|
||||
self.app.config.@"app-notifications".@"clipboard-copy")
|
||||
{
|
||||
if (self.container.window()) |window|
|
||||
window.sendToast("Copied to clipboard");
|
||||
window.sendToast(i18n._("Copied to clipboard"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ const TabView = @import("TabView.zig");
|
||||
const HeaderBar = @import("headerbar.zig");
|
||||
const version = @import("version.zig");
|
||||
const winproto = @import("winproto.zig");
|
||||
const i18n = @import("i18n.zig");
|
||||
|
||||
const log = std.log.scoped(.gtk);
|
||||
|
||||
@@ -192,7 +193,7 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
|
||||
{
|
||||
const btn = c.gtk_menu_button_new();
|
||||
c.gtk_widget_set_tooltip_text(btn, "Main Menu");
|
||||
c.gtk_widget_set_tooltip_text(btn, i18n._("Main Menu"));
|
||||
c.gtk_menu_button_set_icon_name(@ptrCast(btn), "open-menu-symbolic");
|
||||
c.gtk_menu_button_set_popover(@ptrCast(btn), @ptrCast(@alignCast(self.titlebar_menu.asWidget())));
|
||||
_ = c.g_signal_connect_data(
|
||||
@@ -212,7 +213,7 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
const btn = switch (self.config.gtk_tabs_location) {
|
||||
.top, .bottom => btn: {
|
||||
const btn = c.gtk_toggle_button_new();
|
||||
c.gtk_widget_set_tooltip_text(btn, "View Open Tabs");
|
||||
c.gtk_widget_set_tooltip_text(btn, i18n._("View Open Tabs"));
|
||||
c.gtk_button_set_icon_name(@ptrCast(btn), "view-grid-symbolic");
|
||||
_ = c.g_object_bind_property(
|
||||
btn,
|
||||
@@ -239,7 +240,7 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
|
||||
{
|
||||
const btn = c.gtk_button_new_from_icon_name("tab-new-symbolic");
|
||||
c.gtk_widget_set_tooltip_text(btn, "New Tab");
|
||||
c.gtk_widget_set_tooltip_text(btn, i18n._("New Tab"));
|
||||
_ = c.g_signal_connect_data(btn, "clicked", c.G_CALLBACK(>kTabNewClick), self, null, c.G_CONNECT_DEFAULT);
|
||||
self.headerbar.packStart(btn);
|
||||
}
|
||||
@@ -257,7 +258,7 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
// This is a really common issue where people build from source in debug and performance is really bad.
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
const warning_box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
|
||||
const warning_text = "⚠️ You're running a debug build of Ghostty! Performance will be degraded.";
|
||||
const warning_text = i18n._("⚠️ You're running a debug build of Ghostty! Performance will be degraded.");
|
||||
if (adwaita.versionAtLeast(1, 3, 0)) {
|
||||
const banner = c.adw_banner_new(warning_text);
|
||||
c.adw_banner_set_revealed(@ptrCast(banner), 1);
|
||||
@@ -674,10 +675,10 @@ pub fn focusCurrentTab(self: *Window) void {
|
||||
}
|
||||
|
||||
pub fn onConfigReloaded(self: *Window) void {
|
||||
self.sendToast("Reloaded the configuration");
|
||||
self.sendToast(i18n._("Reloaded the configuration"));
|
||||
}
|
||||
|
||||
pub fn sendToast(self: *Window, title: [:0]const u8) void {
|
||||
pub fn sendToast(self: *Window, title: [*:0]const u8) void {
|
||||
const toast = c.adw_toast_new(title);
|
||||
c.adw_toast_set_timeout(toast, 3);
|
||||
c.adw_toast_overlay_add_toast(@ptrCast(self.toast_overlay), toast);
|
||||
@@ -930,7 +931,7 @@ fn gtkActionAbout(
|
||||
"application-name",
|
||||
name,
|
||||
"developer-name",
|
||||
"Ghostty Developers",
|
||||
i18n._("Ghostty Developers"),
|
||||
"application-icon",
|
||||
icon,
|
||||
"version",
|
||||
@@ -949,7 +950,7 @@ fn gtkActionAbout(
|
||||
"logo-icon-name",
|
||||
icon,
|
||||
"title",
|
||||
"About Ghostty",
|
||||
i18n._("About Ghostty"),
|
||||
"version",
|
||||
build_config.version_string.ptr,
|
||||
"website",
|
||||
|
||||
37
src/apprt/gtk/i18n.zig
Normal file
37
src/apprt/gtk/i18n.zig
Normal file
@@ -0,0 +1,37 @@
|
||||
//! I18n support for the GTK frontend based on gettext/libintl
|
||||
//!
|
||||
//! This is normally built into the C standard library for the *vast* majority
|
||||
//! of users who use glibc, but for musl users we fall back to the `gettext-tiny`
|
||||
//! stub implementation which provides all of the necessary interfaces.
|
||||
//! Musl users who do want to use localization should know what they need to do.
|
||||
|
||||
const std = @import("std");
|
||||
const global = &@import("../../global.zig").state;
|
||||
const build_config = @import("../../build_config.zig");
|
||||
|
||||
const log = std.log.scoped(.gtk_i18n);
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator) !void {
|
||||
const resources_dir = global.resources_dir orelse {
|
||||
log.warn("resource dir not found; not localizing", .{});
|
||||
return;
|
||||
};
|
||||
const share_dir = std.fs.path.dirname(resources_dir) orelse {
|
||||
log.warn("resource dir not placed in a share/ directory; not localizing", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
const locale_dir = try std.fs.path.joinZ(alloc, &.{ share_dir, "locale" });
|
||||
defer alloc.free(locale_dir);
|
||||
|
||||
// The only way these calls can fail is if we're out of memory
|
||||
_ = bindtextdomain(build_config.bundle_id, locale_dir.ptr) orelse return error.OutOfMemory;
|
||||
_ = textdomain(build_config.bundle_id) orelse return error.OutOfMemory;
|
||||
}
|
||||
|
||||
// Manually include function definitions for the gettext functions
|
||||
// as libintl.h isn't always easily available (e.g. in musl)
|
||||
extern fn bindtextdomain(domainname: [*:0]const u8, dirname: [*:0]const u8) ?[*:0]const u8;
|
||||
extern fn textdomain(domainname: [*:0]const u8) ?[*:0]const u8;
|
||||
pub extern fn gettext(msgid: [*:0]const u8) [*:0]const u8;
|
||||
pub const _ = gettext;
|
||||
Reference in New Issue
Block a user