mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
feat: initial implementation of i18n/l10n
This commit is contained in:
@ -41,6 +41,10 @@
|
||||
.url = "git+https://github.com/natecraddock/zf/?ref=main#ed99ca18b02dda052e20ba467e90b623c04690dd",
|
||||
.hash = "1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8",
|
||||
},
|
||||
.zig_gettext = .{
|
||||
.url = "git+https://github.com/pluiedev/zig-gettext/?ref=main#c1ff3a954ba0d9ad369ccf96810437c7e46cfc2a",
|
||||
.hash = "122046140255a2dadfc3b3f55a68e750776f0ae2330865665f892d381d8d8f438c69",
|
||||
},
|
||||
|
||||
// C libs
|
||||
.cimgui = .{ .path = "./pkg/cimgui" },
|
||||
@ -48,6 +52,7 @@
|
||||
.freetype = .{ .path = "./pkg/freetype" },
|
||||
.harfbuzz = .{ .path = "./pkg/harfbuzz" },
|
||||
.highway = .{ .path = "./pkg/highway" },
|
||||
.libintl = .{ .path = "./pkg/libintl" },
|
||||
.libpng = .{ .path = "./pkg/libpng" },
|
||||
.macos = .{ .path = "./pkg/macos" },
|
||||
.oniguruma = .{ .path = "./pkg/oniguruma" },
|
||||
|
@ -54,6 +54,7 @@
|
||||
wayland,
|
||||
wayland-scanner,
|
||||
wayland-protocols,
|
||||
gettext,
|
||||
}: let
|
||||
# See package.nix. Keep in sync.
|
||||
rpathLibs =
|
||||
@ -84,6 +85,7 @@
|
||||
gtk4
|
||||
glib
|
||||
wayland
|
||||
gettext
|
||||
];
|
||||
in
|
||||
mkShell {
|
||||
|
@ -20,6 +20,7 @@
|
||||
pkg-config,
|
||||
zig_0_13,
|
||||
pandoc,
|
||||
gettext,
|
||||
revision ? "dirty",
|
||||
optimize ? "Debug",
|
||||
enableX11 ? true,
|
||||
@ -56,6 +57,7 @@
|
||||
../images
|
||||
../include
|
||||
../pkg
|
||||
../po
|
||||
../src
|
||||
../vendor
|
||||
../build.zig
|
||||
@ -125,6 +127,7 @@ in
|
||||
pkg-config
|
||||
zig_hook
|
||||
wrapGAppsHook4
|
||||
gettext
|
||||
]
|
||||
++ lib.optionals enableWayland [
|
||||
wayland-scanner
|
||||
|
@ -1,3 +1,3 @@
|
||||
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
|
||||
# more details.
|
||||
"sha256-Bjy31evaKgpRX1mGwAFkai44eiiorTV1gW3VdP9Ins8="
|
||||
"sha256-ar4f1+7Mx45K446X2/4/n9gVFZklAcQyJHQ+Ehn2p4U="
|
||||
|
40
pkg/libintl/build.zig
Normal file
40
pkg/libintl/build.zig
Normal file
@ -0,0 +1,40 @@
|
||||
const std = @import("std");
|
||||
|
||||
const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{
|
||||
.preferred_link_mode = .dynamic,
|
||||
.search_strategy = .mode_first,
|
||||
};
|
||||
|
||||
pub fn build(b: *std.Build) !void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const module = b.addModule("intl", .{
|
||||
.root_source_file = b.path("main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
if (b.systemIntegrationOption("libintl", .{
|
||||
.default = !target.result.isGnuLibC(),
|
||||
})) {
|
||||
// On non-glibc platforms we don't have libintl
|
||||
// built into libc, so we have to do more work.
|
||||
// In GNU's infinite wisdom, there's no easy pkg-config file for
|
||||
// you to consume and integrate into build systems other than autoconf.
|
||||
// Users must rely on system library/include paths, or manually
|
||||
// add libintl to the Zig search path.
|
||||
module.linkSystemLibrary("intl", dynamic_link_opts);
|
||||
}
|
||||
|
||||
// switch (target.result.os.tag) {
|
||||
// .windows => {
|
||||
// const msys2 = b.dependency("libintl_msys2", .{});
|
||||
// lib.addLibraryPath(msys2.path("usr/bin"));
|
||||
// module.linkSystemLibrary2("msys-intl-8", .{
|
||||
// .preferred_link_mode = .dynamic,
|
||||
// .search_strategy = .mode_first,
|
||||
// });
|
||||
// },
|
||||
// }
|
||||
}
|
6
pkg/libintl/build.zig.zon
Normal file
6
pkg/libintl/build.zig.zon
Normal file
@ -0,0 +1,6 @@
|
||||
.{
|
||||
.name = "libintl",
|
||||
.version = "0.0.1",
|
||||
.paths = .{""},
|
||||
.dependencies = .{},
|
||||
}
|
41
pkg/libintl/c.zig
Normal file
41
pkg/libintl/c.zig
Normal file
@ -0,0 +1,41 @@
|
||||
pub const locale = @cImport(@cInclude("locale.h"));
|
||||
|
||||
pub extern fn gettext(
|
||||
msgid: [*:0]const u8,
|
||||
) [*:0]const u8;
|
||||
pub extern fn dgettext(
|
||||
domainname: [*:0]const u8,
|
||||
msgid: [*:0]const u8,
|
||||
) [*:0]const u8;
|
||||
pub extern fn dcgettext(
|
||||
domainname: [*:0]const u8,
|
||||
msgid: [*:0]const u8,
|
||||
category: c_int,
|
||||
) [*:0]const u8;
|
||||
|
||||
pub extern fn ngettext(
|
||||
msgid1: [*:0]const u8,
|
||||
msgid2: [*:0]const u8,
|
||||
n: c_ulong,
|
||||
) [*:0]const u8;
|
||||
pub extern fn dngettext(
|
||||
domainname: [*:0]const u8,
|
||||
msgid1: [*:0]const u8,
|
||||
msgid2: [*:0]const u8,
|
||||
n: c_ulong,
|
||||
) [*:0]const u8;
|
||||
pub extern fn dcngettext(
|
||||
domainname: [*:0]const u8,
|
||||
msgid1: [*:0]const u8,
|
||||
msgid2: [*:0]const u8,
|
||||
n: c_ulong,
|
||||
category: c_int,
|
||||
) [*:0]const u8;
|
||||
|
||||
pub extern fn bindtextdomain(
|
||||
domainname: [*:0]const u8,
|
||||
dirname: [*:0]const u8,
|
||||
) ?[*]const u8;
|
||||
pub extern fn textdomain(
|
||||
domainname: ?[*:0]const u8,
|
||||
) ?[*]const u8;
|
73
pkg/libintl/main.zig
Normal file
73
pkg/libintl/main.zig
Normal file
@ -0,0 +1,73 @@
|
||||
const std = @import("std");
|
||||
const c = @import("c.zig");
|
||||
|
||||
pub const Category = enum(c_int) {
|
||||
messages = c.locale.LC_MESSAGES,
|
||||
collate = c.locale.LC_COLLATE,
|
||||
ctype = c.locale.LC_CTYPE,
|
||||
monetary = c.locale.LC_MONETARY,
|
||||
numeric = c.locale.LC_NUMERIC,
|
||||
time = c.locale.LC_TIME,
|
||||
_,
|
||||
};
|
||||
|
||||
pub const Query = struct {
|
||||
msg: [:0]const u8,
|
||||
plural: ?struct {
|
||||
msg: [:0]const u8,
|
||||
number: c_ulong,
|
||||
} = null,
|
||||
domain: ?[:0]const u8 = null,
|
||||
category: ?Category = null,
|
||||
};
|
||||
|
||||
pub const _ = gettext;
|
||||
|
||||
pub fn gettext(comptime msg: [:0]const u8) [:0]const u8 {
|
||||
return std.mem.span(c.gettext(msg));
|
||||
}
|
||||
pub fn dgettext(comptime msg: [:0]const u8, domain: [:0]const u8) [:0]const u8 {
|
||||
return std.mem.span(c.dgettext(domain, msg));
|
||||
}
|
||||
pub fn dcgettext(comptime msg: [:0]const u8, domain: [:0]const u8, category: Category) [:0]const u8 {
|
||||
return std.mem.span(c.dcgettext(domain, msg, category));
|
||||
}
|
||||
pub fn ngettext(
|
||||
comptime msg: [:0]const u8,
|
||||
comptime msg_plural: [:0]const u8,
|
||||
number: c_ulong,
|
||||
) [:0]const u8 {
|
||||
return std.mem.span(c.ngettext(msg, msg_plural, number));
|
||||
}
|
||||
pub fn dngettext(
|
||||
comptime msg: [:0]const u8,
|
||||
comptime msg_plural: [:0]const u8,
|
||||
number: c_ulong,
|
||||
domain: [:0]const u8,
|
||||
) [:0]const u8 {
|
||||
return std.mem.span(c.dngettext(domain, msg, msg_plural, number));
|
||||
}
|
||||
pub fn dcngettext(
|
||||
comptime msg: [:0]const u8,
|
||||
comptime msg_plural: [:0]const u8,
|
||||
number: c_ulong,
|
||||
domain: [:0]const u8,
|
||||
category: Category,
|
||||
) [:0]const u8 {
|
||||
return std.mem.span(c.dcngettext(
|
||||
domain,
|
||||
msg,
|
||||
msg_plural,
|
||||
number,
|
||||
category,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn bindTextDomain(domain: [:0]const u8, dir: [:0]const u8) std.mem.Allocator.Error!void {
|
||||
// ENOMEM is the only possible error
|
||||
if (c.bindtextdomain(domain, dir) == null) return error.OutOfMemory;
|
||||
}
|
||||
pub fn setTextDomain(domain: [:0]const u8) std.mem.Allocator.Error!void {
|
||||
// ENOMEM is the only possible error
|
||||
if (c.textdomain(domain) == null) return error.OutOfMemory;
|
||||
}
|
0
po/LINGUAS
Normal file
0
po/LINGUAS
Normal file
159
po/messages.pot
Normal file
159
po/messages.pot
Normal file
@ -0,0 +1,159 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: src/apprt/gtk/App.zig:1827 src/apprt/gtk/Window.zig:918
|
||||
msgid "About Ghostty"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Tab.zig:147
|
||||
msgid "All terminal sessions in this tab will be terminated."
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:800
|
||||
msgid "All terminal sessions in this window will be terminated."
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ClipboardConfirmationWindow.zig:170
|
||||
msgid "Allow"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ClipboardConfirmationWindow.zig:248
|
||||
msgid ""
|
||||
"An application is attempting to read from the clipboard.\n"
|
||||
"The current clipboard contents are shown below.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ClipboardConfirmationWindow.zig:252
|
||||
msgid ""
|
||||
"An application is attempting to write to the clipboard.\n"
|
||||
"The content to write is shown below.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ClipboardConfirmationWindow.zig:237
|
||||
msgid "Authorize Clipboard Access"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ClipboardConfirmationWindow.zig:169
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/App.zig:1814
|
||||
msgid "Close Tab"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/App.zig:1817
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Tab.zig:143
|
||||
msgid "Close this tab?"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:728
|
||||
msgid "Close this terminal?"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:796
|
||||
msgid "Close this window?"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1141
|
||||
msgid "Copied to clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/App.zig:1848
|
||||
msgid "Copy"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ClipboardConfirmationWindow.zig:170
|
||||
msgid "Deny"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:899
|
||||
msgid "Ghostty Developers"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/inspector.zig:143
|
||||
msgid "Ghostty: Terminal Inspector"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/App.zig:1874
|
||||
msgid "Menu"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/App.zig:1813 src/apprt/gtk/Window.zig:201
|
||||
msgid "New Tab"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/App.zig:1812
|
||||
msgid "New Window"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/App.zig:1825
|
||||
msgid "Open Configuration"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/App.zig:1849 src/apprt/gtk/ClipboardConfirmationWindow.zig:169
|
||||
msgid "Paste"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ClipboardConfirmationWindow.zig:245
|
||||
msgid ""
|
||||
"Pasting this text into the terminal may be dangerous as it looks like some commands may be executed.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/App.zig:1826
|
||||
msgid "Reload Configuration"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/App.zig:1864
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/App.zig:1816 src/apprt/gtk/App.zig:1857
|
||||
msgid "Split Down"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/App.zig:1815 src/apprt/gtk/App.zig:1856
|
||||
msgid "Split Right"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/App.zig:1824 src/apprt/gtk/App.zig:1865
|
||||
msgid "Terminal Inspector"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:732
|
||||
msgid ""
|
||||
"There is still a running process in the terminal. Closing the terminal will kill this process.\n"
|
||||
"Are you sure you want to close the terminal?\n"
|
||||
"\n"
|
||||
"Click 'No' to cancel and return to your terminal.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:174
|
||||
msgid "View Open Tabs"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ClipboardConfirmationWindow.zig:236
|
||||
msgid "Warning: Potentially Unsafe Paste"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ResizeOverlay.zig:107
|
||||
msgid "c"
|
||||
msgid_plural "c"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: src/apprt/gtk/ResizeOverlay.zig:111
|
||||
msgid "r"
|
||||
msgid_plural "r"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:220
|
||||
msgid "⚠️ You're running a debug build of Ghostty! Performance will be degraded."
|
||||
msgstr ""
|
@ -38,6 +38,7 @@ const inspector = @import("inspector.zig");
|
||||
const key = @import("key.zig");
|
||||
const winproto = @import("winproto.zig");
|
||||
const testing = std.testing;
|
||||
const intl = @import("intl");
|
||||
|
||||
const log = std.log.scoped(.gtk);
|
||||
|
||||
@ -1809,22 +1810,22 @@ fn initMenuContent(menu: *c.GMenu) void {
|
||||
const section = c.g_menu_new();
|
||||
defer c.g_object_unref(section);
|
||||
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
|
||||
c.g_menu_append(section, "New Window", "win.new_window");
|
||||
c.g_menu_append(section, "New Tab", "win.new_tab");
|
||||
c.g_menu_append(section, "Close Tab", "win.close_tab");
|
||||
c.g_menu_append(section, "Split Right", "win.split_right");
|
||||
c.g_menu_append(section, "Split Down", "win.split_down");
|
||||
c.g_menu_append(section, "Close Window", "win.close");
|
||||
c.g_menu_append(section, intl._("New Window"), "win.new_window");
|
||||
c.g_menu_append(section, intl._("New Tab"), "win.new_tab");
|
||||
c.g_menu_append(section, intl._("Close Tab"), "win.close_tab");
|
||||
c.g_menu_append(section, intl._("Split Right"), "win.split_right");
|
||||
c.g_menu_append(section, intl._("Split Down"), "win.split_down");
|
||||
c.g_menu_append(section, intl._("Close Window"), "win.close");
|
||||
}
|
||||
|
||||
{
|
||||
const section = c.g_menu_new();
|
||||
defer c.g_object_unref(section);
|
||||
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
|
||||
c.g_menu_append(section, "Terminal Inspector", "win.toggle_inspector");
|
||||
c.g_menu_append(section, "Open Configuration", "app.open-config");
|
||||
c.g_menu_append(section, "Reload Configuration", "app.reload-config");
|
||||
c.g_menu_append(section, "About Ghostty", "win.about");
|
||||
c.g_menu_append(section, intl._("Terminal Inspector"), "win.toggle_inspector");
|
||||
c.g_menu_append(section, intl._("Open Configuration"), "app.open-config");
|
||||
c.g_menu_append(section, intl._("Reload Configuration"), "app.reload-config");
|
||||
c.g_menu_append(section, intl._("About Ghostty"), "win.about");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1845,24 +1846,24 @@ fn initContextMenu(self: *App) void {
|
||||
const section = c.g_menu_new();
|
||||
defer c.g_object_unref(section);
|
||||
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
|
||||
c.g_menu_append(section, "Copy", "win.copy");
|
||||
c.g_menu_append(section, "Paste", "win.paste");
|
||||
c.g_menu_append(section, intl._("Copy"), "win.copy");
|
||||
c.g_menu_append(section, intl._("Paste"), "win.paste");
|
||||
}
|
||||
|
||||
{
|
||||
const section = c.g_menu_new();
|
||||
defer c.g_object_unref(section);
|
||||
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
|
||||
c.g_menu_append(section, "Split Right", "win.split_right");
|
||||
c.g_menu_append(section, "Split Down", "win.split_down");
|
||||
c.g_menu_append(section, intl._("Split Right"), "win.split_right");
|
||||
c.g_menu_append(section, intl._("Split Down"), "win.split_down");
|
||||
}
|
||||
|
||||
{
|
||||
const section = c.g_menu_new();
|
||||
defer c.g_object_unref(section);
|
||||
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
|
||||
c.g_menu_append(section, "Reset", "win.reset");
|
||||
c.g_menu_append(section, "Terminal Inspector", "win.toggle_inspector");
|
||||
c.g_menu_append(section, intl._("Reset"), "win.reset");
|
||||
c.g_menu_append(section, intl._("Terminal Inspector"), "win.toggle_inspector");
|
||||
}
|
||||
|
||||
const section = c.g_menu_new();
|
||||
@ -1871,7 +1872,7 @@ fn initContextMenu(self: *App) void {
|
||||
defer c.g_object_unref(submenu);
|
||||
|
||||
initMenuContent(@ptrCast(submenu));
|
||||
c.g_menu_append_submenu(section, "Menu", @ptrCast(@alignCast(submenu)));
|
||||
c.g_menu_append_submenu(section, intl._("Menu"), @ptrCast(@alignCast(submenu)));
|
||||
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
|
||||
|
||||
self.context_menu = menu;
|
||||
|
@ -9,6 +9,7 @@ const CoreSurface = @import("../../Surface.zig");
|
||||
const App = @import("App.zig");
|
||||
const View = @import("View.zig");
|
||||
const c = @import("c.zig").c;
|
||||
const intl = @import("intl");
|
||||
|
||||
const log = std.log.scoped(.gtk);
|
||||
|
||||
@ -166,8 +167,8 @@ const ButtonsView = struct {
|
||||
|
||||
pub fn init(root: *ClipboardConfirmation) !ButtonsView {
|
||||
const cancel_text, const confirm_text = switch (root.pending_req) {
|
||||
.paste => .{ "Cancel", "Paste" },
|
||||
.osc_52_read, .osc_52_write => .{ "Deny", "Allow" },
|
||||
.paste => .{ intl._("Cancel"), intl._("Paste") },
|
||||
.osc_52_read, .osc_52_write => .{ intl._("Deny"), intl._("Allow") },
|
||||
};
|
||||
|
||||
const cancel_button = c.gtk_button_new_with_label(cancel_text);
|
||||
@ -233,8 +234,8 @@ const ButtonsView = struct {
|
||||
/// The title of the window, based on the reason the prompt is being shown.
|
||||
fn titleText(req: apprt.ClipboardRequest) [:0]const u8 {
|
||||
return switch (req) {
|
||||
.paste => "Warning: Potentially Unsafe Paste",
|
||||
.osc_52_read, .osc_52_write => "Authorize Clipboard Access",
|
||||
.paste => intl._("Warning: Potentially Unsafe Paste"),
|
||||
.osc_52_read, .osc_52_write => intl._("Authorize Clipboard Access"),
|
||||
};
|
||||
}
|
||||
|
||||
@ -242,16 +243,16 @@ fn titleText(req: apprt.ClipboardRequest) [:0]const u8 {
|
||||
/// is being shown.
|
||||
fn promptText(req: apprt.ClipboardRequest) [:0]const u8 {
|
||||
return switch (req) {
|
||||
.paste =>
|
||||
\\Pasting this text into the terminal may be dangerous as it looks like some commands may be executed.
|
||||
,
|
||||
.osc_52_read =>
|
||||
\\An application is attempting to read from the clipboard.
|
||||
\\The current clipboard contents are shown below.
|
||||
,
|
||||
.osc_52_write =>
|
||||
\\An application is attempting to write to the clipboard.
|
||||
\\The content to write is shown below.
|
||||
,
|
||||
.paste => intl._(
|
||||
\\Pasting this text into the terminal may be dangerous as it looks like some commands may be executed.
|
||||
),
|
||||
.osc_52_read => intl._(
|
||||
\\An application is attempting to read from the clipboard.
|
||||
\\The current clipboard contents are shown below.
|
||||
),
|
||||
.osc_52_write => intl._(
|
||||
\\An application is attempting to write to the clipboard.
|
||||
\\The content to write is shown below.
|
||||
),
|
||||
};
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ const std = @import("std");
|
||||
const c = @import("c.zig").c;
|
||||
const configpkg = @import("../../config.zig");
|
||||
const Surface = @import("Surface.zig");
|
||||
const intl = @import("intl");
|
||||
|
||||
const log = std.log.scoped(.gtk);
|
||||
|
||||
@ -95,13 +96,20 @@ fn gtkUpdate(ud: ?*anyopaque) callconv(.C) c.gboolean {
|
||||
};
|
||||
|
||||
const grid_size = surface.core_surface.size.grid();
|
||||
var buf: [32]u8 = undefined;
|
||||
|
||||
var buf: [64]u8 = undefined;
|
||||
const text = std.fmt.bufPrintZ(
|
||||
&buf,
|
||||
"{d}c ⨯ {d}r",
|
||||
"{d}{s} ⨯ {d}{s}",
|
||||
.{
|
||||
grid_size.columns,
|
||||
// Translators: the abbreviation for "column" (of a grid) in your language.
|
||||
// If the abbreviation cannot be intuitively understood, use the full word.
|
||||
intl.ngettext("c", "c", grid_size.columns),
|
||||
grid_size.rows,
|
||||
// Translators: the abbreviation for "row" (of a grid) in your language.
|
||||
// If the abbreviation cannot be intuitively understood, use the full word.
|
||||
intl.ngettext("r", "r", grid_size.rows),
|
||||
},
|
||||
) catch |err| {
|
||||
log.err("unable to format text: {}", .{err});
|
||||
|
@ -25,6 +25,7 @@ const ResizeOverlay = @import("ResizeOverlay.zig");
|
||||
const inspector = @import("inspector.zig");
|
||||
const gtk_key = @import("key.zig");
|
||||
const c = @import("c.zig").c;
|
||||
const intl = @import("intl");
|
||||
|
||||
const log = std.log.scoped(.gtk_surface);
|
||||
|
||||
@ -725,14 +726,16 @@ pub fn close(self: *Surface, processActive: bool) void {
|
||||
c.GTK_DIALOG_MODAL,
|
||||
c.GTK_MESSAGE_QUESTION,
|
||||
c.GTK_BUTTONS_YES_NO,
|
||||
"Close this terminal?",
|
||||
intl._("Close this terminal?"),
|
||||
);
|
||||
c.gtk_message_dialog_format_secondary_text(
|
||||
@ptrCast(alert),
|
||||
"There is still a running process in the terminal. " ++
|
||||
"Closing the terminal will kill this process. " ++
|
||||
"Are you sure you want to close the terminal?\n\n" ++
|
||||
"Click 'No' to cancel and return to your terminal.",
|
||||
intl._(
|
||||
\\There is still a running process in the terminal. Closing the terminal will kill this process.
|
||||
\\Are you sure you want to close the terminal?
|
||||
\\
|
||||
\\Click 'No' to cancel and return to your terminal.
|
||||
),
|
||||
);
|
||||
|
||||
// We want the "yes" to appear destructive.
|
||||
@ -1136,7 +1139,7 @@ pub fn setClipboardString(
|
||||
self.app.config.@"app-notifications".@"clipboard-copy")
|
||||
{
|
||||
if (self.container.window()) |window|
|
||||
window.sendToast("Copied to clipboard");
|
||||
window.sendToast(intl._("Copied to clipboard"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ const CoreSurface = @import("../../Surface.zig");
|
||||
const Surface = @import("Surface.zig");
|
||||
const Window = @import("Window.zig");
|
||||
const c = @import("c.zig").c;
|
||||
const intl = @import("intl");
|
||||
|
||||
const log = std.log.scoped(.gtk);
|
||||
|
||||
@ -140,11 +141,11 @@ pub fn closeWithConfirmation(tab: *Tab) void {
|
||||
c.GTK_DIALOG_MODAL,
|
||||
c.GTK_MESSAGE_QUESTION,
|
||||
c.GTK_BUTTONS_YES_NO,
|
||||
"Close this tab?",
|
||||
intl._("Close this tab?"),
|
||||
);
|
||||
c.gtk_message_dialog_format_secondary_text(
|
||||
@ptrCast(alert),
|
||||
"All terminal sessions in this tab will be terminated.",
|
||||
intl._("All terminal sessions in this tab will be terminated."),
|
||||
);
|
||||
|
||||
// We want the "yes" to appear destructive.
|
||||
|
@ -26,6 +26,7 @@ const Notebook = @import("notebook.zig").Notebook;
|
||||
const HeaderBar = @import("headerbar.zig").HeaderBar;
|
||||
const version = @import("version.zig");
|
||||
const winproto = @import("winproto.zig");
|
||||
const intl = @import("intl");
|
||||
|
||||
const log = std.log.scoped(.gtk);
|
||||
|
||||
@ -171,7 +172,7 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
const btn = switch (app.config.@"gtk-tabs-location") {
|
||||
.top, .bottom, .left, .right => 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, intl._("View Open Tabs"));
|
||||
c.gtk_button_set_icon_name(@ptrCast(btn), "view-grid-symbolic");
|
||||
_ = c.g_object_bind_property(
|
||||
btn,
|
||||
@ -198,7 +199,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, intl._("New Tab"));
|
||||
_ = c.g_signal_connect_data(btn, "clicked", c.G_CALLBACK(>kTabNewClick), self, null, c.G_CONNECT_DEFAULT);
|
||||
self.headerbar.packStart(btn);
|
||||
}
|
||||
@ -217,7 +218,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 = intl._("⚠️ You're running a debug build of Ghostty! Performance will be degraded.");
|
||||
if ((comptime adwaita.versionAtLeast(1, 3, 0)) and
|
||||
adwaita.enabled(&app.config) and
|
||||
adwaita.versionAtLeast(1, 3, 0))
|
||||
@ -793,11 +794,11 @@ fn gtkCloseRequest(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
|
||||
c.GTK_DIALOG_MODAL,
|
||||
c.GTK_MESSAGE_QUESTION,
|
||||
c.GTK_BUTTONS_YES_NO,
|
||||
"Close this window?",
|
||||
intl._("Close this window?"),
|
||||
);
|
||||
c.gtk_message_dialog_format_secondary_text(
|
||||
@ptrCast(alert),
|
||||
"All terminal sessions in this window will be terminated.",
|
||||
intl._("All terminal sessions in this window will be terminated."),
|
||||
);
|
||||
|
||||
// We want the "yes" to appear destructive.
|
||||
@ -896,7 +897,7 @@ fn gtkActionAbout(
|
||||
"application-name",
|
||||
name,
|
||||
"developer-name",
|
||||
"Ghostty Developers",
|
||||
intl._("Ghostty Developers").ptr,
|
||||
"application-icon",
|
||||
icon,
|
||||
"version",
|
||||
@ -915,7 +916,7 @@ fn gtkActionAbout(
|
||||
"logo-icon-name",
|
||||
icon,
|
||||
"title",
|
||||
"About Ghostty",
|
||||
intl._("About Ghostty").ptr,
|
||||
"version",
|
||||
build_config.version_string.ptr,
|
||||
"website",
|
||||
|
@ -9,6 +9,7 @@ const TerminalWindow = @import("Window.zig");
|
||||
const ImguiWidget = @import("ImguiWidget.zig");
|
||||
const c = @import("c.zig").c;
|
||||
const CoreInspector = @import("../../inspector/main.zig").Inspector;
|
||||
const intl = @import("intl");
|
||||
|
||||
const log = std.log.scoped(.inspector);
|
||||
|
||||
@ -140,7 +141,7 @@ const Window = struct {
|
||||
const gtk_window: *c.GtkWindow = @ptrCast(window);
|
||||
errdefer c.gtk_window_destroy(gtk_window);
|
||||
self.window = gtk_window;
|
||||
c.gtk_window_set_title(gtk_window, "Ghostty: Terminal Inspector");
|
||||
c.gtk_window_set_title(gtk_window, intl._("Ghostty: Terminal Inspector"));
|
||||
c.gtk_window_set_default_size(gtk_window, 1000, 600);
|
||||
c.gtk_window_set_icon_name(gtk_window, build_config.bundle_id);
|
||||
c.gtk_widget_add_css_class(@ptrCast(@alignCast(gtk_window)), "window");
|
||||
|
@ -14,6 +14,64 @@ pub fn init(b: *std.Build, cfg: *const Config) !GhosttyResources {
|
||||
var steps = std.ArrayList(*std.Build.Step).init(b.allocator);
|
||||
errdefer steps.deinit();
|
||||
|
||||
// Localization files (.pot, .po, .mo)
|
||||
{
|
||||
const update = b.step("update-translations", "Update translation files");
|
||||
|
||||
const gettext = b.dependency("zig_gettext", .{
|
||||
// We're running this on the host, so we need to compile it for the host
|
||||
.target = b.graph.host,
|
||||
.optimize = cfg.optimize,
|
||||
});
|
||||
|
||||
const xgettext = b.addRunArtifact(gettext.artifact("xgettext"));
|
||||
const pot = pot: {
|
||||
var src_files = try b.build_root.handle.openDir("src", .{ .iterate = true });
|
||||
defer src_files.close();
|
||||
|
||||
var walk = try src_files.walk(b.allocator);
|
||||
defer walk.deinit();
|
||||
|
||||
while (try walk.next()) |src| {
|
||||
switch (src.kind) {
|
||||
.file => if (!std.mem.endsWith(u8, src.basename, ".zig")) continue,
|
||||
else => continue,
|
||||
}
|
||||
xgettext.addFileArg(b.path(b.pathJoin(&.{ "src", src.path })));
|
||||
}
|
||||
break :pot xgettext.captureStdOut();
|
||||
};
|
||||
|
||||
// TODO: Use UpdateSourceFiles when Zig 0.14 releases
|
||||
var wf_update = b.addWriteFiles();
|
||||
wf_update.addCopyFileToSource(pot, "po/messages.pot");
|
||||
update.dependOn(&wf_update.step);
|
||||
|
||||
var wf_mo = b.addWriteFiles();
|
||||
var linguas = try b.build_root.handle.openFile("po/LINGUAS", .{});
|
||||
defer linguas.close();
|
||||
var reader = linguas.reader();
|
||||
var buf: [64]u8 = undefined;
|
||||
|
||||
while (try reader.readUntilDelimiterOrEof(&buf, '\n')) |locale| {
|
||||
const po = b.fmt("po/{s}.po", .{locale});
|
||||
|
||||
const mo = b.addRunArtifact(gettext.artifact("msgfmt"));
|
||||
mo.addFileArg(b.path(po));
|
||||
_ = wf_mo.addCopyFile(
|
||||
mo.captureStdOut(),
|
||||
b.pathJoin(&.{ "share", "locale", locale, "LC_MESSAGES", "messages.mo" }),
|
||||
);
|
||||
|
||||
const msgmerge = b.addSystemCommand(&.{ "msgmerge", "--update", "--quiet" });
|
||||
msgmerge.addFileArg(b.path(po));
|
||||
msgmerge.addFileArg(pot);
|
||||
update.dependOn(&msgmerge.step);
|
||||
}
|
||||
|
||||
try steps.append(&wf_mo.step);
|
||||
}
|
||||
|
||||
// Terminfo
|
||||
terminfo: {
|
||||
// Encode our terminfo
|
||||
|
@ -358,6 +358,10 @@ pub fn add(
|
||||
.optimize = optimize,
|
||||
.with_tui = false,
|
||||
}).module("zf"));
|
||||
step.root_module.addImport("intl", b.dependency("libintl", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
}).module("intl"));
|
||||
|
||||
// Mac Stuff
|
||||
if (step.rootModuleTarget().isDarwin()) {
|
||||
|
@ -10,6 +10,7 @@ const oni = @import("oniguruma");
|
||||
const crash = @import("crash/main.zig");
|
||||
const renderer = @import("renderer.zig");
|
||||
const xev = @import("xev");
|
||||
const intl = @import("intl");
|
||||
|
||||
/// Global process state. This is initialized in main() for exe artifacts
|
||||
/// and by ghostty_init() for lib artifacts. This should ONLY be used by
|
||||
@ -162,6 +163,8 @@ pub const GlobalState = struct {
|
||||
// hereafter can use this cached value.
|
||||
self.resources_dir = try internal_os.resourcesDir(self.alloc);
|
||||
errdefer if (self.resources_dir) |dir| self.alloc.free(dir);
|
||||
|
||||
try self.initI18n();
|
||||
}
|
||||
|
||||
/// Cleans up the global state. This doesn't _need_ to be called but
|
||||
@ -200,6 +203,20 @@ pub const GlobalState = struct {
|
||||
std.log.warn("failed to ignore SIGPIPE err={}", .{err});
|
||||
};
|
||||
}
|
||||
fn initI18n(self: *GlobalState) std.mem.Allocator.Error!void {
|
||||
const resources = self.resources_dir orelse {
|
||||
std.log.warn("resources dir not found: not localizing", .{});
|
||||
return;
|
||||
};
|
||||
const share = std.fs.path.dirname(resources) orelse return;
|
||||
const locale = try std.fs.path.joinZ(self.alloc, &.{ share, "locale" });
|
||||
defer self.alloc.free(locale);
|
||||
|
||||
std.log.warn("locale={s}", .{locale});
|
||||
|
||||
try intl.bindTextDomain("messages", locale);
|
||||
try intl.setTextDomain("messages");
|
||||
}
|
||||
};
|
||||
|
||||
/// Maintains the Unix resource limits that we set for our process. This
|
||||
|
@ -9,14 +9,13 @@ const Allocator = std.mem.Allocator;
|
||||
/// This is highly Ghostty-specific and can likely be generalized at
|
||||
/// some point but we can cross that bridge if we ever need to.
|
||||
pub fn resourcesDir(alloc: std.mem.Allocator) !?[]const u8 {
|
||||
// If we have an environment variable set, we always use that.
|
||||
// Note: we ALWAYS want to allocate here because the result is always
|
||||
// freed, do not try to use internal_os.getenv or posix getenv.
|
||||
if (std.process.getEnvVarOwned(alloc, "GHOSTTY_RESOURCES_DIR")) |dir| {
|
||||
if (dir.len > 0) return dir;
|
||||
} else |err| switch (err) {
|
||||
error.EnvironmentVariableNotFound => {},
|
||||
else => return err,
|
||||
// If we have an environment variable set, we prefer that *only* in release mode.
|
||||
//
|
||||
// The reason is that debug builds built by developers may have updated
|
||||
// resources, and debug Ghostty launched from release Ghostty should not
|
||||
// inherit old/stale resources of release Ghostty.
|
||||
if (comptime builtin.mode != .Debug) {
|
||||
if (try getDirFromEnv(alloc)) |dir| return dir;
|
||||
}
|
||||
|
||||
// This is the sentinel value we look for in the path to know
|
||||
@ -52,6 +51,25 @@ pub fn resourcesDir(alloc: std.mem.Allocator) !?[]const u8 {
|
||||
}
|
||||
}
|
||||
|
||||
// If we are in debug mode and we couldn't find freshly-built
|
||||
// resources for some reason, we fall back to using the env var
|
||||
if (comptime builtin.mode == .Debug) {
|
||||
if (try getDirFromEnv(alloc)) |dir| return dir;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn getDirFromEnv(alloc: Allocator) !?[]u8 {
|
||||
// Note: we ALWAYS want to allocate here because the result is always
|
||||
// freed, do not try to use internal_os.getenv or posix getenv.
|
||||
if (std.process.getEnvVarOwned(alloc, "GHOSTTY_RESOURCES_DIR")) |dir| {
|
||||
if (dir.len > 0) return dir;
|
||||
} else |err| switch (err) {
|
||||
error.EnvironmentVariableNotFound => {},
|
||||
else => return err,
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user