From 314d52ac3a122b79d8d7db92c356b7ea8e0ed9e5 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Sat, 22 Mar 2025 08:28:56 -0400 Subject: [PATCH 01/73] shell-integration: switch to $GHOSTTY_SHELL_FEATURES This change consolidates all three opt-out shell integration environment variables into a single opt-in $GHOSTTY_SHELL_FEATURES variable. Its value is a comma-delimited list of the enabled shell feature names (e.g. "cursor,title"). $GHOSTTY_SHELL_FEATURES is set at runtime and automatically added to the shell environment. Its value is based on the shell-integration-features configuration option. $GHOSTTY_SHELL_FEATURES is only set when at least one shell feature is enabled. It won't be set when 'shell-integration-features = false'. $GHOSTTY_SHELL_FEATURES lists only the enabled shell feature names. We could have alternatively gone in the opposite direction and listed the disabled features, letting the scripts assume each feature is on by default like we did before, but I think this explicit approach is a little safer and easier to reason about / debug. It also doesn't support the "no-" negation prefix used by the config system (e.g. "cursor,no-title"). This simplifies the implementation requirements of our (multiple) shell integration scripts, and because $GHOSTTY_SHELL_FEATURES is derived from shell-integration-features, the user-facing configuration interface retains that expressiveness. $GHOSTTY_SHELL_FEATURES is intended to primarily be an internal concern: an interface between the runtime and our shell integration scripts. It could be used by people with particular use cases who want to manually source those scripts, but that isn't the intended audience. ... and because the previous $GHOSTTY_SHELL_INTEGRATION_NO_* variables were also meant to be an internal concern, this change does not include backwards compatibility support for those names. One last advantage of a using a single $GHOSTTY_SHELL_FEATURES variable is that it can be easily forwarded to e.g. ssh sessions or other shell environments. --- src/shell-integration/bash/ghostty.bash | 8 ++--- .../elvish/lib/ghostty-integration.elv | 14 +++++---- .../ghostty-shell-integration.fish | 14 ++++----- src/shell-integration/zsh/ghostty-integration | 6 ++-- src/termio/shell_integration.zig | 29 ++++++++++--------- 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/shell-integration/bash/ghostty.bash b/src/shell-integration/bash/ghostty.bash index 7fae435a3..0cfd41663 100644 --- a/src/shell-integration/bash/ghostty.bash +++ b/src/shell-integration/bash/ghostty.bash @@ -70,7 +70,7 @@ if [ -n "$GHOSTTY_BASH_INJECT" ]; then fi # Sudo -if [[ "$GHOSTTY_SHELL_INTEGRATION_NO_SUDO" != "1" && -n "$TERMINFO" ]]; then +if [[ "$GHOSTTY_SHELL_FEATURES" == *"sudo"* && -n "$TERMINFO" ]]; then # Wrap `sudo` command to ensure Ghostty terminfo is preserved. # # This approach supports wrapping a `sudo` alias, but the alias definition @@ -124,13 +124,13 @@ function __ghostty_precmd() { fi # Cursor - if test "$GHOSTTY_SHELL_INTEGRATION_NO_CURSOR" != "1"; then + if [[ "$GHOSTTY_SHELL_FEATURES" == *"cursor"* ]]; then PS1=$PS1'\[\e[5 q\]' PS0=$PS0'\[\e[0 q\]' fi # Title (working directory) - if [[ "$GHOSTTY_SHELL_INTEGRATION_NO_TITLE" != 1 ]]; then + if [[ "$GHOSTTY_SHELL_FEATURES" == *"title"* ]]; then PS1=$PS1'\[\e]2;\w\a\]' fi fi @@ -161,7 +161,7 @@ function __ghostty_preexec() { PS2="$_GHOSTTY_SAVE_PS2" # Title (current command) - if [[ -n $cmd && "$GHOSTTY_SHELL_INTEGRATION_NO_TITLE" != 1 ]]; then + if [[ -n $cmd && "$GHOSTTY_SHELL_FEATURES" == *"title"* ]]; then builtin printf "\e]2;%s\a" "${cmd//[[:cntrl:]]}" fi diff --git a/src/shell-integration/elvish/lib/ghostty-integration.elv b/src/shell-integration/elvish/lib/ghostty-integration.elv index 08fe42f3f..7d0bc2d83 100644 --- a/src/shell-integration/elvish/lib/ghostty-integration.elv +++ b/src/shell-integration/elvish/lib/ghostty-integration.elv @@ -36,6 +36,8 @@ } { + use str + # helper used by `mark-*` functions fn set-prompt-state {|new| set-env __ghostty_prompt_state $new } @@ -104,20 +106,20 @@ set edit:after-readline = (conj $edit:after-readline $mark-output-start~) set edit:after-command = (conj $edit:after-command $mark-output-end~) - var no-title = (eq 1 $E:GHOSTTY_SHELL_INTEGRATION_NO_TITLE) - var no-cursor = (eq 1 $E:GHOSTTY_SHELL_INTEGRATION_NO_CURSOR) - var no-sudo = (eq 1 $E:GHOSTTY_SHELL_INTEGRATION_NO_SUDO) + var title = (str:contains $E:GHOSTTY_SHELL_FEATURES "title") + var cursor = (str:contains $E:GHOSTTY_SHELL_FEATURES "cursor") + var sudo = (str:contains $E:GHOSTTY_SHELL_FEATURES "sudo") - if (not $no-title) { + if $title { set after-chdir = (conj $after-chdir {|_| report-pwd }) } - if (not $no-cursor) { + if $cursor { fn beam { printf "\e[5 q" } fn block { printf "\e[0 q" } set edit:before-readline = (conj $edit:before-readline $beam~) set edit:after-readline = (conj $edit:after-readline {|_| block }) } - if (and (not $no-sudo) (not-eq "" $E:TERMINFO) (has-external sudo)) { + if (and $sudo (not-eq "" $E:TERMINFO) (has-external sudo)) { edit:add-var sudo~ $sudo-with-terminfo~ } } diff --git a/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish b/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish index cd4f56105..770c8c781 100644 --- a/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish +++ b/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish @@ -49,10 +49,10 @@ status --is-interactive || ghostty_exit function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration" functions -e __ghostty_setup - # Check if we are setting cursors - set --local no_cursor "$GHOSTTY_SHELL_INTEGRATION_NO_CURSOR" + set --local cursor string match -q "*cursor*" "$GHOSTTY_SHELL_FEATURES" + set --local sudo string match -q "*sudo*" "$GHOSTTY_SHELL_FEATURES" - if test -z $no_cursor + if $cursor # Change the cursor to a beam on prompt. function __ghostty_set_cursor_beam --on-event fish_prompt -d "Set cursor shape" echo -en "\e[5 q" @@ -62,13 +62,9 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration" end end - # Check if we are setting sudo - set --local no_sudo "$GHOSTTY_SHELL_INTEGRATION_NO_SUDO" - # When using sudo shell integration feature, ensure $TERMINFO is set # and `sudo` is not already a function or alias - if test -z $no_sudo - and test -n "$TERMINFO"; and test "file" = (type -t sudo 2> /dev/null; or echo "x") + if $sudo and test -n "$TERMINFO"; and test "file" = (type -t sudo 2> /dev/null; or echo "x") # Wrap `sudo` command to ensure Ghostty terminfo is preserved function sudo -d "Wrap sudo to preserve terminfo" set --function sudo_has_sudoedit_flags "no" @@ -125,7 +121,7 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration" set --global fish_handle_reflow 1 # Initial calls for first prompt - if test -z $no_cursor + if $cursor __ghostty_set_cursor_beam end __ghostty_mark_prompt_start diff --git a/src/shell-integration/zsh/ghostty-integration b/src/shell-integration/zsh/ghostty-integration index 9eebe1a30..c1329683e 100644 --- a/src/shell-integration/zsh/ghostty-integration +++ b/src/shell-integration/zsh/ghostty-integration @@ -194,7 +194,7 @@ _ghostty_deferred_init() { _ghostty_report_pwd" _ghostty_report_pwd - if [[ "$GHOSTTY_SHELL_INTEGRATION_NO_TITLE" != 1 ]]; then + if [[ "$GHOSTTY_SHELL_FEATURES" == *"title"* ]]; then # Enable terminal title changes. functions[_ghostty_precmd]+=" builtin print -rnu $_ghostty_fd \$'\\e]2;'\"\${(%):-%(4~|…/%3~|%~)}\"\$'\\a'" @@ -202,7 +202,7 @@ _ghostty_deferred_init() { builtin print -rnu $_ghostty_fd \$'\\e]2;'\"\${(V)1}\"\$'\\a'" fi - if [[ "$GHOSTTY_SHELL_INTEGRATION_NO_CURSOR" != 1 ]]; then + if [[ "$GHOSTTY_SHELL_FEATURES" == *"cursor"* ]]; then # Enable cursor shape changes depending on the current keymap. # This implementation leaks blinking block cursor into external commands # executed from zle. For example, users of fzf-based widgets may find @@ -221,7 +221,7 @@ _ghostty_deferred_init() { fi # Sudo - if [[ "$GHOSTTY_SHELL_INTEGRATION_NO_SUDO" != "1" ]] && [[ -n "$TERMINFO" ]]; then + if [[ "$GHOSTTY_SHELL_FEATURES" == *"sudo"* ]] && [[ -n "$TERMINFO" ]]; then # Wrap `sudo` command to ensure Ghostty terminfo is preserved sudo() { builtin local sudo_has_sudoedit_flags="no" diff --git a/src/termio/shell_integration.zig b/src/termio/shell_integration.zig index 4bbf0a3b5..d87762dbc 100644 --- a/src/termio/shell_integration.zig +++ b/src/termio/shell_integration.zig @@ -150,9 +150,18 @@ pub fn setupFeatures( env: *EnvMap, features: config.ShellIntegrationFeatures, ) !void { - if (!features.cursor) try env.put("GHOSTTY_SHELL_INTEGRATION_NO_CURSOR", "1"); - if (!features.sudo) try env.put("GHOSTTY_SHELL_INTEGRATION_NO_SUDO", "1"); - if (!features.title) try env.put("GHOSTTY_SHELL_INTEGRATION_NO_TITLE", "1"); + var enabled = try std.BoundedArray(u8, 256).init(0); + + inline for (@typeInfo(@TypeOf(features)).@"struct".fields) |f| { + if (@field(features, f.name)) { + if (enabled.len > 0) try enabled.append(','); + try enabled.appendSlice(f.name); + } + } + + if (enabled.len > 0) { + try env.put("GHOSTTY_SHELL_FEATURES", enabled.slice()); + } } test "setup features" { @@ -162,15 +171,13 @@ test "setup features" { defer arena.deinit(); const alloc = arena.allocator(); - // Test: all features enabled (no environment variables should be set) + // Test: all features enabled { var env = EnvMap.init(alloc); defer env.deinit(); try setupFeatures(&env, .{ .cursor = true, .sudo = true, .title = true }); - try testing.expect(env.get("GHOSTTY_SHELL_INTEGRATION_NO_CURSOR") == null); - try testing.expect(env.get("GHOSTTY_SHELL_INTEGRATION_NO_SUDO") == null); - try testing.expect(env.get("GHOSTTY_SHELL_INTEGRATION_NO_TITLE") == null); + try testing.expectEqualStrings("cursor,sudo,title", env.get("GHOSTTY_SHELL_FEATURES").?); } // Test: all features disabled @@ -179,9 +186,7 @@ test "setup features" { defer env.deinit(); try setupFeatures(&env, .{ .cursor = false, .sudo = false, .title = false }); - try testing.expectEqualStrings("1", env.get("GHOSTTY_SHELL_INTEGRATION_NO_CURSOR").?); - try testing.expectEqualStrings("1", env.get("GHOSTTY_SHELL_INTEGRATION_NO_SUDO").?); - try testing.expectEqualStrings("1", env.get("GHOSTTY_SHELL_INTEGRATION_NO_TITLE").?); + try testing.expect(env.get("GHOSTTY_SHELL_FEATURES") == null); } // Test: mixed features @@ -190,9 +195,7 @@ test "setup features" { defer env.deinit(); try setupFeatures(&env, .{ .cursor = false, .sudo = true, .title = false }); - try testing.expectEqualStrings("1", env.get("GHOSTTY_SHELL_INTEGRATION_NO_CURSOR").?); - try testing.expect(env.get("GHOSTTY_SHELL_INTEGRATION_NO_SUDO") == null); - try testing.expectEqualStrings("1", env.get("GHOSTTY_SHELL_INTEGRATION_NO_TITLE").?); + try testing.expectEqualStrings("sudo", env.get("GHOSTTY_SHELL_FEATURES").?); } } From 77dc5c9dd2b11501950ecdaafe5f0cebd3732d5d Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Sat, 22 Mar 2025 12:11:51 -0400 Subject: [PATCH 02/73] shell-integration: use fish's native list type Instead of looking for individual substrings in $GHOSTTY_SHELL_FEATURES, `string split` it into a list of feature names and use `contains` to detect their presence. --- .../fish/vendor_conf.d/ghostty-shell-integration.fish | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish b/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish index 770c8c781..35cea144a 100644 --- a/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish +++ b/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish @@ -49,10 +49,9 @@ status --is-interactive || ghostty_exit function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration" functions -e __ghostty_setup - set --local cursor string match -q "*cursor*" "$GHOSTTY_SHELL_FEATURES" - set --local sudo string match -q "*sudo*" "$GHOSTTY_SHELL_FEATURES" + set --local features (string split , $GHOSTTY_SHELL_FEATURES) - if $cursor + if contains cursor $features # Change the cursor to a beam on prompt. function __ghostty_set_cursor_beam --on-event fish_prompt -d "Set cursor shape" echo -en "\e[5 q" @@ -64,7 +63,7 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration" # When using sudo shell integration feature, ensure $TERMINFO is set # and `sudo` is not already a function or alias - if $sudo and test -n "$TERMINFO"; and test "file" = (type -t sudo 2> /dev/null; or echo "x") + if contains sudo $features and test -n "$TERMINFO"; and test "file" = (type -t sudo 2> /dev/null; or echo "x") # Wrap `sudo` command to ensure Ghostty terminfo is preserved function sudo -d "Wrap sudo to preserve terminfo" set --function sudo_has_sudoedit_flags "no" @@ -121,7 +120,7 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration" set --global fish_handle_reflow 1 # Initial calls for first prompt - if $cursor + if contains cursor $features __ghostty_set_cursor_beam end __ghostty_mark_prompt_start From 36ff70eb7ff693684fd2f74ec55f7f161e9f477e Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Sat, 22 Mar 2025 12:26:56 -0400 Subject: [PATCH 03/73] shell-integration: use elvish's native list type Instead of looking for individual substrings in $GHOSTTY_SHELL_FEATURES, `str:split` it into a list of feature names and use `has-value` to detect their presence. --- .../elvish/lib/ghostty-integration.elv | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/shell-integration/elvish/lib/ghostty-integration.elv b/src/shell-integration/elvish/lib/ghostty-integration.elv index 7d0bc2d83..b0de19d3f 100644 --- a/src/shell-integration/elvish/lib/ghostty-integration.elv +++ b/src/shell-integration/elvish/lib/ghostty-integration.elv @@ -106,20 +106,18 @@ set edit:after-readline = (conj $edit:after-readline $mark-output-start~) set edit:after-command = (conj $edit:after-command $mark-output-end~) - var title = (str:contains $E:GHOSTTY_SHELL_FEATURES "title") - var cursor = (str:contains $E:GHOSTTY_SHELL_FEATURES "cursor") - var sudo = (str:contains $E:GHOSTTY_SHELL_FEATURES "sudo") + var features = [(str:split ',' $E:GHOSTTY_SHELL_FEATURES)] - if $title { + if (has-value $features title) { set after-chdir = (conj $after-chdir {|_| report-pwd }) } - if $cursor { + if (has-value $features cursor) { fn beam { printf "\e[5 q" } fn block { printf "\e[0 q" } set edit:before-readline = (conj $edit:before-readline $beam~) set edit:after-readline = (conj $edit:after-readline {|_| block }) } - if (and $sudo (not-eq "" $E:TERMINFO) (has-external sudo)) { + if (and (has-value $features sudo) (not-eq "" $E:TERMINFO) (has-external sudo)) { edit:add-var sudo~ $sudo-with-terminfo~ } } From 0caba3e19f6d656e57a74c2d0ea899133305cd5c Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Sat, 22 Mar 2025 15:54:48 -0400 Subject: [PATCH 04/73] shell-integration: comptime buffer capacity --- src/termio/shell_integration.zig | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/termio/shell_integration.zig b/src/termio/shell_integration.zig index d87762dbc..c02351801 100644 --- a/src/termio/shell_integration.zig +++ b/src/termio/shell_integration.zig @@ -150,17 +150,23 @@ pub fn setupFeatures( env: *EnvMap, features: config.ShellIntegrationFeatures, ) !void { - var enabled = try std.BoundedArray(u8, 256).init(0); + const fields = @typeInfo(@TypeOf(features)).@"struct".fields; + const capacity: usize = capacity: { + comptime var n: usize = fields.len - 1; // commas + inline for (fields) |field| n += field.name.len; + break :capacity n; + }; + var buffer = try std.BoundedArray(u8, capacity).init(0); - inline for (@typeInfo(@TypeOf(features)).@"struct".fields) |f| { - if (@field(features, f.name)) { - if (enabled.len > 0) try enabled.append(','); - try enabled.appendSlice(f.name); + inline for (fields) |field| { + if (@field(features, field.name)) { + if (buffer.len > 0) try buffer.append(','); + try buffer.appendSlice(field.name); } } - if (enabled.len > 0) { - try env.put("GHOSTTY_SHELL_FEATURES", enabled.slice()); + if (buffer.len > 0) { + try env.put("GHOSTTY_SHELL_FEATURES", buffer.slice()); } } From cd6b850758b6c19aa7f081fee24ea6bd8d5296ea Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Sat, 22 Mar 2025 15:57:04 -0400 Subject: [PATCH 05/73] shell-integration: minor documentation updates --- src/termio/shell_integration.zig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/termio/shell_integration.zig b/src/termio/shell_integration.zig index c02351801..ae8d5b67c 100644 --- a/src/termio/shell_integration.zig +++ b/src/termio/shell_integration.zig @@ -30,7 +30,7 @@ pub const ShellIntegration = struct { command: []const u8, }; -/// Setup the command execution environment for automatic +/// Set up the command execution environment for automatic /// integrated shell integration and return a ShellIntegration /// struct describing the integration. If integration fails /// (shell type couldn't be detected, etc.), this will return null. @@ -144,8 +144,7 @@ test "force shell" { } } -/// Setup shell integration feature environment variables without -/// performing full shell integration setup. +/// Set up the shell integration features environment variable. pub fn setupFeatures( env: *EnvMap, features: config.ShellIntegrationFeatures, From 416b617de90903da7204e46398fe1941b35604e9 Mon Sep 17 00:00:00 2001 From: Aaron Ruan Date: Tue, 25 Mar 2025 22:46:14 +0800 Subject: [PATCH 06/73] fix canonicalizing some zh locales (#6885) resolve #6870 --------- Signed-off-by: Aaron Ruan --- src/os/i18n.zig | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/os/i18n.zig b/src/os/i18n.zig index a21a26be8..baae73e46 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -107,6 +107,9 @@ pub fn canonicalizeLocale( buf: []u8, locale: []const u8, ) error{NoSpaceLeft}![:0]const u8 { + // Fix zh locales for macOS + if (fixZhLocale(locale)) |fixed| return fixed; + // Buffer must be 16 or at least as long as the locale and null term if (buf.len < @max(16, locale.len + 1)) return error.NoSpaceLeft; @@ -125,6 +128,30 @@ pub fn canonicalizeLocale( return buf[0..slice.len :0]; } +/// Handles some zh locales canonicalization because internal libintl +/// canonicalization function doesn't handle correctly in these cases. +fn fixZhLocale(locale: []const u8) ?[:0]const u8 { + var it = std.mem.splitScalar(u8, locale, '-'); + const name = it.next() orelse return null; + if (!std.mem.eql(u8, name, "zh")) return null; + + const script = it.next() orelse return null; + const region = it.next() orelse return null; + + if (std.mem.eql(u8, script, "Hans")) { + if (std.mem.eql(u8, region, "SG")) return "zh_SG"; + return "zh_CN"; + } + + if (std.mem.eql(u8, script, "Hant")) { + if (std.mem.eql(u8, region, "MO")) return "zh_MO"; + if (std.mem.eql(u8, region, "HK")) return "zh_HK"; + return "zh_TW"; + } + + return null; +} + /// This can be called at any point a compile-time-known locale is /// available. This will use comptime to verify the locale is supported. pub fn staticLocale(comptime v: [*:0]const u8) [*:0]const u8 { @@ -159,6 +186,12 @@ test "canonicalizeLocale darwin" { try testing.expectEqualStrings("zh_CN", try canonicalizeLocale(&buf, "zh-Hans")); try testing.expectEqualStrings("zh_TW", try canonicalizeLocale(&buf, "zh-Hant")); + try testing.expectEqualStrings("zh_CN", try canonicalizeLocale(&buf, "zh-Hans-CN")); + try testing.expectEqualStrings("zh_SG", try canonicalizeLocale(&buf, "zh-Hans-SG")); + try testing.expectEqualStrings("zh_TW", try canonicalizeLocale(&buf, "zh-Hant-TW")); + try testing.expectEqualStrings("zh_HK", try canonicalizeLocale(&buf, "zh-Hant-HK")); + try testing.expectEqualStrings("zh_MO", try canonicalizeLocale(&buf, "zh-Hant-MO")); + // This is just an edge case I want to make sure we're aware of: // canonicalizeLocale does not handle encodings and will turn them into // underscores. We should parse them out before calling this function. From 67f47a6e226cdfb52570ff0222246170f16ccdd5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 25 Mar 2025 14:53:14 -0700 Subject: [PATCH 07/73] macos: remove special-case cmd+period handling Fixes #5522 This commit re-dispatches command inputs that are unhandled by our macOS app so they can be encoded to the pty and handled by the core libghostty key callback system. We've had a special case `cmd+period` handling in Ghostty for a very long time (since well into the private beta). `cmd+period` by default binds to "cancel" in macOS, so it doesn't encode to the pty. We don't handle "cancel" in any meaningful way in Ghostty, so we special-cased it to encode properly to the pty. However, as shown in #5522, if the user rebinds `cmd+period` at the system level to some other operation, then this is ignored and we encode it still. This isn't desirable, we just want to work around not caring about "cancel." The callback path that AppKit takes for key events is a bit convoluted. For command keys, it first calls `performKeyEquivalent`. If this returns false (we want to continue standard processing), then it calls EITHER `keyDown` or `doCommand(by:)`. It calls the latter if there is a standard system command that matches the key event. For `cmd+period` by default, this is "cancel." Unfortunately, from `doCommand` we can't say "oops, we don't want to handle this, please continue processing." Its too late. So, this commit stores the last command key event from `performKeyEquivalent` and if we reach `doCommand` for it without having called `keyDown`, we re-dispatch the event and send it to keyDown. I'm honestly pretty sus about this whole logic but it is scoped to only command-keys and I couldn't trigger any adverse behavior in my testing. It also definitely fixed #5522 as far as I could reproduce it before. --- .../Sources/Ghostty/SurfaceView_AppKit.swift | 77 +++++++++++++++++-- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index e4c9072f3..60a573c37 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -922,6 +922,33 @@ extension Ghostty { _ = keyAction(GHOSTTY_ACTION_RELEASE, event: event) } + /// Records the timestamp of the last event to performKeyEquivalent that had a command key active. + /// + /// For command+key inputs, the AppKit input stack calls performKeyEquivalent to give us a chance + /// to handle them first. If we return "false" then it goes through the standard AppKit responder chain. + /// For an NSTextInputClient, that may redirect some commands _before_ our keyDown gets called. + /// Concretely: Command+Period will do: performKeyEquivalent, doCommand ("cancel:"). In doCommand, + /// we need to know that we actually want to handle that in keyDown, so we send it back through the + /// event dispatch system and use this timestamp as an identity to know to actually send it to keyDown. + /// + /// Why not send it to keyDown always? Because if the user rebinds a command to something we + /// actually handle then we do want the standard response chain to handle the key input. Unfortunately, + /// we can't know what a command is bound to at a system level until we let it flow through the system. + /// That's the crux of the problem. + /// + /// So, we have to send it back through if we didn't handle it. + /// + /// The next part of the problem is comparing NSEvent identity seems pretty nasty. I couldn't + /// find a good way to do it. I originally stored a weak ref and did identity comparison but that + /// doesn't work and for reasons I couldn't figure out the value gets mangled (fields don't match + /// before/after the assignment). I suspect it has something to do with the fact an NSEvent is wrapping + /// a lower level event pointer and its just not surviving the Swift runtime somehow. I don't know. + /// + /// The best thing I could find was to store the event timestamp which has decent granularity + /// and compare that. To further complicate things, some events are synthetic and have a zero + /// timestamp so we have to protect against that. Fun! + var lastCommandEvent: TimeInterval? + /// Special case handling for some control keys override func performKeyEquivalent(with event: NSEvent) -> Bool { switch (event.type) { @@ -975,15 +1002,41 @@ extension Ghostty { equivalent = "\r" - case ".": - if (!event.modifierFlags.contains(.command)) { + default: + // It looks like some part of AppKit sometimes generates synthetic NSEvents + // with a zero timestamp. We never process these at this point. Concretely, + // this happens for me when pressing Cmd+period with default bindings. This + // binds to "cancel" which goes through AppKit to produce a synthetic "escape". + // + // Question: should we be ignoring all synthetic events? Should we be finding + // synthetic escape and ignoring it? I feel like Cmd+period could map to a + // escape binding by accident, but it hasn't happened yet... + if event.timestamp == 0 { return false } - equivalent = "." + // All of this logic here re: lastCommandEvent is to workaround some + // nasty behavior. See the docs for lastCommandEvent for more info. - default: - // Ignore other events + // Ignore all other non-command events. This lets the event continue + // through the AppKit event systems. + if (!event.modifierFlags.contains(.command)) { + // Reset since we got a non-command event. + lastCommandEvent = nil + return false + } + + // If we have a prior command binding and the timestamp matches exactly + // then we pass it through to keyDown for encoding. + if let lastCommandEvent { + self.lastCommandEvent = nil + if lastCommandEvent == event.timestamp { + equivalent = event.characters ?? "" + break + } + } + + lastCommandEvent = event.timestamp return false } @@ -1480,9 +1533,19 @@ extension Ghostty.SurfaceView: NSTextInputClient { } } + /// This function needs to exist for two reasons: + /// 1. Prevents an audible NSBeep for unimplemented actions. + /// 2. Allows us to properly encode super+key input events that we don't handle override func doCommand(by selector: Selector) { - // This currently just prevents NSBeep from interpretKeyEvents but in the future - // we may want to make some of this work. + // If we are being processed by performKeyEquivalent with a command binding, + // we send it back through the event system so it can be encoded. + if let lastCommandEvent, + let current = NSApp.currentEvent, + lastCommandEvent == current.timestamp + { + NSApp.sendEvent(current) + return + } print("SEL: \(selector)") } From 6f9a362a4d152d59488528254e04416c318f3ad4 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Tue, 25 Mar 2025 20:57:08 -0600 Subject: [PATCH 08/73] fix(Metal): fix crunchy appearance of constrained glyphs We can't use nearest neighbor filtering for sampling from the atlas because we might not actually be doing pixel perfect sampling if the glyph has been constrained. This will change in the future, but this will have to be set to linear for now. --- src/renderer/shaders/cell.metal | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/renderer/shaders/cell.metal b/src/renderer/shaders/cell.metal index e24ddcb1e..84212ad3a 100644 --- a/src/renderer/shaders/cell.metal +++ b/src/renderer/shaders/cell.metal @@ -499,7 +499,11 @@ fragment float4 cell_text_fragment( constexpr sampler textureSampler( coord::pixel, address::clamp_to_edge, - filter::nearest + // TODO(qwerasd): This can be changed back to filter::nearest when + // we move the constraint logic out of the GPU code + // which should once again guarantee pixel perfect + // sizing. + filter::linear ); switch (in.mode) { From e96f94d8d775ba9007318fc1543d7f41b2288fe8 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Tue, 25 Mar 2025 20:57:53 -0600 Subject: [PATCH 09/73] fix(Metal): improve constraint width logic to account for x offset --- src/renderer/shaders/cell.metal | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/renderer/shaders/cell.metal b/src/renderer/shaders/cell.metal index 84212ad3a..e80ead9ad 100644 --- a/src/renderer/shaders/cell.metal +++ b/src/renderer/shaders/cell.metal @@ -425,11 +425,19 @@ vertex CellTextVertexOut cell_text_vertex( // If we're constrained then we need to scale the glyph. if (in.mode == MODE_TEXT_CONSTRAINED) { float max_width = uniforms.cell_size.x * in.constraint_width; + // If this glyph is wider than the constraint width, + // fit it to the width and remove its horizontal offset. if (size.x > max_width) { float new_y = size.y * (max_width / size.x); offset.y += (size.y - new_y) / 2; + offset.x = 0; size.y = new_y; size.x = max_width; + } else if (max_width - size.x > offset.x) { + // However, if it does fit in the constraint width, make + // sure the offset is small enough to not push it over the + // right edge of the constraint width. + offset.x = max_width - size.x; } } From 447a889559bd7939e2ac63fd64eaef11b1367ac4 Mon Sep 17 00:00:00 2001 From: Ian den Hartog Date: Wed, 26 Mar 2025 19:24:31 +0100 Subject: [PATCH 10/73] fix: add ( and ) to escape characters when dropping files in gtk --- src/os/shell.zig | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/os/shell.zig b/src/os/shell.zig index 23648a82a..7ae5c4857 100644 --- a/src/os/shell.zig +++ b/src/os/shell.zig @@ -23,6 +23,8 @@ pub fn ShellEscapeWriter(comptime T: type) type { '?', ' ', '|', + '(', + ')', => &[_]u8{ '\\', byte }, else => &[_]u8{byte}, }; @@ -93,3 +95,12 @@ test "shell escape 6" { try writer.writeAll("a\"c"); try testing.expectEqualStrings("a\\\"c", fmt.getWritten()); } + +test "shell escape 7" { + var buf: [128]u8 = undefined; + var fmt = std.io.fixedBufferStream(&buf); + var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() }; + const writer = shell.writer(); + try writer.writeAll("a(1)"); + try testing.expectEqualStrings("a\(1\)", fmt.getWritten()); +} From 18cc119ced71353229a12dd98480e6ad225b509d Mon Sep 17 00:00:00 2001 From: Ian den Hartog Date: Wed, 26 Mar 2025 19:52:36 +0100 Subject: [PATCH 11/73] fix: escape characters in shell escape test --- src/os/shell.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/os/shell.zig b/src/os/shell.zig index 7ae5c4857..d4d682d3e 100644 --- a/src/os/shell.zig +++ b/src/os/shell.zig @@ -102,5 +102,5 @@ test "shell escape 7" { var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() }; const writer = shell.writer(); try writer.writeAll("a(1)"); - try testing.expectEqualStrings("a\(1\)", fmt.getWritten()); + try testing.expectEqualStrings("a\\(1\\)", fmt.getWritten()); } From ae3e92a3fb3e74ed591b97fc5f10b7ea45b371c0 Mon Sep 17 00:00:00 2001 From: Leah Amelia Chen Date: Wed, 26 Mar 2025 19:24:36 +0100 Subject: [PATCH 12/73] gtk: use up-to-date maximized & fullscreen state in syncAppearance DerivedConfig's maximize and fullscreen should only ever be used during window creation and nowhere else. --- src/apprt/gtk/Window.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 2866c5ede..d8e64a980 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -473,11 +473,13 @@ pub fn syncAppearance(self: *Window) !void { if (self.isQuickTerminal()) break :visible false; // Unconditionally disable the header bar when fullscreened. - if (self.config.fullscreen) break :visible false; + if (self.window.as(gtk.Window).isFullscreen() != 0) + break :visible false; // *Conditionally* disable the header bar when maximized, // and gtk-titlebar-hide-when-maximized is set - if (self.config.maximize and self.config.gtk_titlebar_hide_when_maximized) + if (self.window.as(gtk.Window).isMaximized() != 0 and + self.config.gtk_titlebar_hide_when_maximized) break :visible false; break :visible self.config.gtk_titlebar; @@ -672,7 +674,7 @@ pub fn toggleTabOverview(self: *Window) void { /// Toggle the maximized state for this window. pub fn toggleMaximize(self: *Window) void { - if (self.config.maximize) { + if (self.window.as(gtk.Window).isMaximized() != 0) { self.window.as(gtk.Window).unmaximize(); } else { self.window.as(gtk.Window).maximize(); @@ -683,7 +685,7 @@ pub fn toggleMaximize(self: *Window) void { /// Toggle fullscreen for this window. pub fn toggleFullscreen(self: *Window) void { - if (self.config.fullscreen) { + if (self.window.as(gtk.Window).isFullscreen() != 0) { self.window.as(gtk.Window).unfullscreen(); } else { self.window.as(gtk.Window).fullscreen(); @@ -754,7 +756,6 @@ fn gtkWindowNotifyMaximized( _: *gobject.ParamSpec, self: *Window, ) callconv(.c) void { - self.config.maximize = self.window.as(gtk.Window).isMaximized() != 0; self.syncAppearance() catch |err| { log.err("failed to sync appearance={}", .{err}); }; @@ -765,7 +766,6 @@ fn gtkWindowNotifyFullscreened( _: *gobject.ParamSpec, self: *Window, ) callconv(.c) void { - self.config.fullscreen = self.window.as(gtk.Window).isFullscreen() != 0; self.syncAppearance() catch |err| { log.err("failed to sync appearance={}", .{err}); }; From 27978ef4d0f1b25891d4f92fbd0dd46ece176198 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Wed, 26 Mar 2025 23:29:15 -0500 Subject: [PATCH 13/73] version bump for development --- build.zig.zon | 2 +- nix/package.nix | 2 +- src/build/Config.zig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 3cd2eed7f..831918f0e 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = .ghostty, - .version = "1.1.3", + .version = "1.1.4", .paths = .{""}, .fingerprint = 0x64407a2a0b4147e5, .dependencies = .{ diff --git a/nix/package.nix b/nix/package.nix index 46bf18122..9368b2cde 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -39,7 +39,7 @@ in stdenv.mkDerivation (finalAttrs: { pname = "ghostty"; - version = "1.1.3"; + version = "1.1.4"; # We limit source like this to try and reduce the amount of rebuilds as possible # thus we only provide the source that is needed for the build diff --git a/src/build/Config.zig b/src/build/Config.zig index 48456734a..8974e1f0c 100644 --- a/src/build/Config.zig +++ b/src/build/Config.zig @@ -19,7 +19,7 @@ const GitVersion = @import("GitVersion.zig"); /// TODO: When Zig 0.14 is released, derive this from build.zig.zon directly. /// Until then this MUST match build.zig.zon and should always be the /// _next_ version to release. -const app_version: std.SemanticVersion = .{ .major = 1, .minor = 1, .patch = 3 }; +const app_version: std.SemanticVersion = .{ .major = 1, .minor = 1, .patch = 4 }; /// Standard build configuration options. optimize: std.builtin.OptimizeMode, From 99843cf54d0ed195505df97e9f5393faa4e4dd58 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 27 Mar 2025 07:19:07 -0700 Subject: [PATCH 14/73] macos: reset the last command key state when keyDown event Fixes a regression from #6909 See #6887 In certain scenarios, the last command key state would linger around (I could only see this happen with global keybinds for unknown reasons yet). This state is only meant to have an effect within the cycle of a single keybind and only so we can ensure an event reaches keyDown so it should be reset if keyDown is ever sent (since, by definition at that point, keyDown has been reached). I'm still not happy that this is necessary and I suspect there is a better root cause to resolve, but I'd rather get this fix in now and figure out the root cause later. --- macos/Sources/Ghostty/SurfaceView_AppKit.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 60a573c37..f04bf1af3 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -884,6 +884,11 @@ extension Ghostty { nil } + // If we are in a keyDown then we don't need to redispatch a command-modded + // key event (see docs for this field) so reset this to nil because + // `interpretKeyEvents` may dispach it. + self.lastCommandEvent = nil + self.interpretKeyEvents([translationEvent]) // If our keyboard changed from this we just assume an input method From 9f57a03926ff9f148441cfbdf2c1b0e3e67e7304 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Fri, 28 Mar 2025 14:31:57 -0500 Subject: [PATCH 15/73] core: update libvaxis and zf for transitive zigimg dependency --- build.zig.zon | 8 ++++---- build.zig.zon.json | 18 +++++++++--------- build.zig.zon.nix | 18 +++++++++--------- build.zig.zon.txt | 6 +++--- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 831918f0e..fb0b8cb1f 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -14,8 +14,8 @@ }, .vaxis = .{ // rockorager/libvaxis - .url = "git+https://github.com/rockorager/libvaxis#4182b7fa42f27cf14a71dbdb54cfd82c5c6e3447", - .hash = "vaxis-0.1.0-BWNV_MHyCAA0rNbPTr50Z44PyEdNP9zQSnHcXBXoo3Ti", + .url = "git+https://github.com/rockorager/libvaxis#1f41c121e8fc153d9ce8c6eb64b2bbab68ad7d23", + .hash = "vaxis-0.1.0-BWNV_FUICQAFZnTCL11TUvnUr1Y0_ZdqtXHhd51d76Rn", .lazy = true, }, .z2d = .{ @@ -48,8 +48,8 @@ }, .zf = .{ // natecraddock/zf - .url = "https://github.com/natecraddock/zf/archive/1039cf75447a8d5b8d481fedb914fe848d246276.tar.gz", - .hash = "zf-0.10.3-OIRy8bKIAADhjqtdjVaDfONRuI7RVl5gMbhCoOwiBWV5", + .url = "https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz", + .hash = "zf-0.10.3-OIRy8aiIAACLrBllz0zjxaH0aOe5oNm3KtEMyCntST-9", .lazy = true, }, .gobject = .{ diff --git a/build.zig.zon.json b/build.zig.zon.json index a4b8f923b..c93f16231 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -104,10 +104,10 @@ "url": "https://deps.files.ghostty.org/utfcpp-1220d4d18426ca72fc2b7e56ce47273149815501d0d2395c2a98c726b31ba931e641.tar.gz", "hash": "sha256-/8ZooxDndgfTk/PBizJxXyI9oerExNbgV5oR345rWc8=" }, - "vaxis-0.1.0-BWNV_MHyCAA0rNbPTr50Z44PyEdNP9zQSnHcXBXoo3Ti": { + "vaxis-0.1.0-BWNV_FUICQAFZnTCL11TUvnUr1Y0_ZdqtXHhd51d76Rn": { "name": "vaxis", - "url": "git+https://github.com/rockorager/libvaxis#4182b7fa42f27cf14a71dbdb54cfd82c5c6e3447", - "hash": "sha256-iONEySjPeD0WYJ93fw5mxT+0pVfUO/m6008J/LXjQkA=" + "url": "git+https://github.com/rockorager/libvaxis#1f41c121e8fc153d9ce8c6eb64b2bbab68ad7d23", + "hash": "sha256-bNZ3oveT6vPChjimPJ/GGfcdivlAeJdl/xfWM+S/MHY=" }, "N-V-__8AAKrHGAAs2shYq8UkE6bGcR1QJtLTyOE_lcosMn6t": { "name": "wayland", @@ -129,10 +129,10 @@ "url": "https://github.com/vancluever/z2d/archive/1e89605a624940c310c7a1d81b46a7c5c05919e3.tar.gz", "hash": "sha256-PEKVSUZ6teRbDyhFPWSiuBSe40pgr0kVRivIY8Cn8HQ=" }, - "zf-0.10.3-OIRy8bKIAADhjqtdjVaDfONRuI7RVl5gMbhCoOwiBWV5": { + "zf-0.10.3-OIRy8aiIAACLrBllz0zjxaH0aOe5oNm3KtEMyCntST-9": { "name": "zf", - "url": "https://github.com/natecraddock/zf/archive/1039cf75447a8d5b8d481fedb914fe848d246276.tar.gz", - "hash": "sha256-xVva07TAYlVv4E4PKe2wUj86a6Ky2YC30YBgtbvNKvw=" + "url": "https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz", + "hash": "sha256-3nulNQd/4rZ4paeXJYXwAliNNyRNsIOX/q3z1JB8C7I=" }, "zg-0.13.4-AAAAAGiZ7QLz4pvECFa_wG4O4TP4FLABHHbemH2KakWM": { "name": "zg", @@ -154,10 +154,10 @@ "url": "https://codeberg.org/ifreund/zig-wayland/archive/f3c5d503e540ada8cbcb056420de240af0c094f7.tar.gz", "hash": "sha256-E77GZ15APYbbO1WzmuJi8eG9/iQFbc2CgkNBxjCLUhk=" }, - "zigimg-0.1.0-lly-O4heEADSRxoTwJwrD3TBfUob9052sIgb9SL8Iz-A": { + "zigimg-0.1.0-lly-O6N2EABOxke8dqyzCwhtUCAafqP35zC7wsZ4Ddxj": { "name": "zigimg", - "url": "git+https://github.com/TUSF/zigimg#0ce4eca3560d5553b13263d6b6bb72e146dd43d0", - "hash": "sha256-Rr+mAfbLOoaxHOwCug+0cWCmW9gDhjhnaO2J/Oik9HI=" + "url": "git+https://github.com/TUSF/zigimg#31268548fe3276c0e95f318a6c0d2ab10565b58d", + "hash": "sha256-oblfr2FIzuqq0FLo/RrzCwUX1NJJuT53EwD3nP3KwN0=" }, "ziglyph-0.11.2-AAAAAHPtHwB4Mbzn1KvOV7Wpjo82NYEc_v0WC8oCLrkf": { "name": "ziglyph", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index f98709d65..407c5da7b 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -250,11 +250,11 @@ in }; } { - name = "vaxis-0.1.0-BWNV_MHyCAA0rNbPTr50Z44PyEdNP9zQSnHcXBXoo3Ti"; + name = "vaxis-0.1.0-BWNV_FUICQAFZnTCL11TUvnUr1Y0_ZdqtXHhd51d76Rn"; path = fetchZigArtifact { name = "vaxis"; - url = "git+https://github.com/rockorager/libvaxis#4182b7fa42f27cf14a71dbdb54cfd82c5c6e3447"; - hash = "sha256-iONEySjPeD0WYJ93fw5mxT+0pVfUO/m6008J/LXjQkA="; + url = "git+https://github.com/rockorager/libvaxis#1f41c121e8fc153d9ce8c6eb64b2bbab68ad7d23"; + hash = "sha256-bNZ3oveT6vPChjimPJ/GGfcdivlAeJdl/xfWM+S/MHY="; }; } { @@ -290,11 +290,11 @@ in }; } { - name = "zf-0.10.3-OIRy8bKIAADhjqtdjVaDfONRuI7RVl5gMbhCoOwiBWV5"; + name = "zf-0.10.3-OIRy8aiIAACLrBllz0zjxaH0aOe5oNm3KtEMyCntST-9"; path = fetchZigArtifact { name = "zf"; - url = "https://github.com/natecraddock/zf/archive/1039cf75447a8d5b8d481fedb914fe848d246276.tar.gz"; - hash = "sha256-xVva07TAYlVv4E4PKe2wUj86a6Ky2YC30YBgtbvNKvw="; + url = "https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz"; + hash = "sha256-3nulNQd/4rZ4paeXJYXwAliNNyRNsIOX/q3z1JB8C7I="; }; } { @@ -330,11 +330,11 @@ in }; } { - name = "zigimg-0.1.0-lly-O4heEADSRxoTwJwrD3TBfUob9052sIgb9SL8Iz-A"; + name = "zigimg-0.1.0-lly-O6N2EABOxke8dqyzCwhtUCAafqP35zC7wsZ4Ddxj"; path = fetchZigArtifact { name = "zigimg"; - url = "git+https://github.com/TUSF/zigimg#0ce4eca3560d5553b13263d6b6bb72e146dd43d0"; - hash = "sha256-Rr+mAfbLOoaxHOwCug+0cWCmW9gDhjhnaO2J/Oik9HI="; + url = "git+https://github.com/TUSF/zigimg#31268548fe3276c0e95f318a6c0d2ab10565b58d"; + hash = "sha256-oblfr2FIzuqq0FLo/RrzCwUX1NJJuT53EwD3nP3KwN0="; }; } { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index b0ea16fa3..f2cc560cc 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -1,6 +1,6 @@ git+https://codeberg.org/atman/zg#4a002763419a34d61dcbb1f415821b83b9bf8ddc -git+https://github.com/TUSF/zigimg#0ce4eca3560d5553b13263d6b6bb72e146dd43d0 -git+https://github.com/rockorager/libvaxis#4182b7fa42f27cf14a71dbdb54cfd82c5c6e3447 +git+https://github.com/TUSF/zigimg#31268548fe3276c0e95f318a6c0d2ab10565b58d +git+https://github.com/rockorager/libvaxis#1f41c121e8fc153d9ce8c6eb64b2bbab68ad7d23 https://codeberg.org/ifreund/zig-wayland/archive/f3c5d503e540ada8cbcb056420de240af0c094f7.tar.gz https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz @@ -30,5 +30,5 @@ https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-2 https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8650079de477e80a5983646e3e4d24cda1dbaefa.tar.gz https://github.com/mitchellh/libxev/archive/3df9337a9e84450a58a2c4af434ec1a036f7b494.tar.gz https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz -https://github.com/natecraddock/zf/archive/1039cf75447a8d5b8d481fedb914fe848d246276.tar.gz +https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz https://github.com/vancluever/z2d/archive/1e89605a624940c310c7a1d81b46a7c5c05919e3.tar.gz From 969839acf30d91f85e88590b4a62f49002ec7ed9 Mon Sep 17 00:00:00 2001 From: Leah Amelia Chen Date: Wed, 2 Apr 2025 14:30:33 +0200 Subject: [PATCH 16/73] gtk(x11): fix blur regions when using >200% scaling See #6957 We were not considering GTK's internal scale factor that converts between "surface coordinates" and actual device coordinates, and that worked fine until the scale factor reached 2x (200%). Since the code is now dependent on the scale factor (which could change at any given moment), we also listen to scale factor changes and then unconditionally call `winproto.syncAppearance`. Even though it's somewhat overkill, I don't expect people to change their scale factor dramatically all the time anyway... --- src/apprt/gtk/Window.zig | 27 +++++++++++++++++++++++++++ src/apprt/gtk/winproto/x11.zig | 17 ++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index d8e64a980..5fcb0d42b 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -281,6 +281,15 @@ pub fn init(self: *Window, app: *App) !void { .detail = "is-active", }, ); + _ = gobject.Object.signals.notify.connect( + self.window, + *Window, + gtkWindowUpdateScaleFactor, + self, + .{ + .detail = "scale-factor", + }, + ); // If Adwaita is enabled and is older than 1.4.0 we don't have the tab overview and so we // need to stick the headerbar into the content box. @@ -784,6 +793,24 @@ fn gtkWindowNotifyIsActive( } } +fn gtkWindowUpdateScaleFactor( + _: *adw.ApplicationWindow, + _: *gobject.ParamSpec, + self: *Window, +) callconv(.c) void { + // On some platforms (namely X11) we need to refresh our appearance when + // the scale factor changes. In theory this could be more fine-grained as + // a full refresh could be expensive, but a) this *should* be rare, and + // b) quite noticeable visual bugs would occur if this is not present. + self.winproto.syncAppearance() catch |err| { + log.err( + "failed to sync appearance after scale factor has been updated={}", + .{err}, + ); + return; + }; +} + // Note: we MUST NOT use the GtkButton parameter because gtkActionNewTab // sends an undefined value. fn gtkTabNewClick(_: *gtk.Button, self: *Window) callconv(.c) void { diff --git a/src/apprt/gtk/winproto/x11.zig b/src/apprt/gtk/winproto/x11.zig index 6d6950f74..c2b6bf416 100644 --- a/src/apprt/gtk/winproto/x11.zig +++ b/src/apprt/gtk/winproto/x11.zig @@ -219,13 +219,12 @@ pub const Window = struct { pub fn resizeEvent(self: *Window) !void { // The blur region must update with window resizes - const gtk_widget = self.gtk_window.as(gtk.Widget); - self.blur_region.width = gtk_widget.getWidth(); - self.blur_region.height = gtk_widget.getHeight(); try self.syncBlur(); } pub fn syncAppearance(self: *Window) !void { + // The user could have toggled between CSDs and SSDs, + // therefore we need to recalculate the blur region offset. self.blur_region = blur: { // NOTE(pluiedev): CSDs are a f--king mistake. // Please, GNOME, stop this nonsense of making a window ~30% bigger @@ -236,6 +235,11 @@ pub const Window = struct { self.gtk_window.as(gtk.Native).getSurfaceTransform(&x, &y); + // Transform surface coordinates to device coordinates. + const scale: f64 = @floatFromInt(self.gtk_window.as(gtk.Widget).getScaleFactor()); + x *= scale; + y *= scale; + break :blur .{ .x = @intFromFloat(x), .y = @intFromFloat(y), @@ -265,6 +269,13 @@ pub const Window = struct { // and I think it's not really noticeable enough to justify the effort. // (Wayland also has this visual artifact anyway...) + const gtk_widget = self.gtk_window.as(gtk.Widget); + + // Transform surface coordinates to device coordinates. + const scale = self.gtk_window.as(gtk.Widget).getScaleFactor(); + self.blur_region.width = gtk_widget.getWidth() * scale; + self.blur_region.height = gtk_widget.getHeight() * scale; + const blur = self.config.background_blur; log.debug("set blur={}, window xid={}, region={}", .{ blur, From af0004eb52eecdcc57e0901f51368d079dc97611 Mon Sep 17 00:00:00 2001 From: Simon Olofsson Date: Thu, 3 Apr 2025 10:58:08 +0200 Subject: [PATCH 17/73] docs: use Command instead of super for macOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Command is the name Apple uses for this key and that's printed on the keyboard 😉 --- src/config/Config.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index ecdcee7fc..9cd285d3b 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -826,7 +826,7 @@ env: RepeatableStringMap = .{}, link: RepeatableLink = .{}, /// Enable URL matching. URLs are matched on hover with control (Linux) or -/// super (macOS) pressed and open using the default system application for +/// command (macOS) pressed and open using the default system application for /// the linked URL. /// /// The URL matcher is always lowest priority of any configured links (see From e19b5a150a3db28275fddcd65073b706547cef20 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 3 Apr 2025 14:56:09 -0400 Subject: [PATCH 18/73] libghostty: Action CValue should be untagged extern union Fixes #6962 I believe this is an upstream bug (https://github.com/ziglang/zig/issues/23454), where Zig is allowing extern unions to be tagged when created via type reification. This results in a CValue that has an extra trailing byte (the tag). This wasn't causing any noticeable issues for Ghostty for some reason but others using our pattern were seeing issues. And I did confirm that our CValue was indeed tagged and was the wrong byte size. I assume Swift was just ignoring it because it was extra data. I don't know, but we should fix this in general for libghostty. --- src/apprt/action.zig | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 2ddbee524..30cb2fa5e 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -311,7 +311,7 @@ pub const Action = union(Key) { break :cvalue @Type(.{ .@"union" = .{ .layout = .@"extern", - .tag_type = Key, + .tag_type = null, .fields = &union_fields, .decls = &.{}, } }); @@ -323,6 +323,13 @@ pub const Action = union(Key) { value: CValue, }; + comptime { + // For ABI compatibility, we expect that this is our union size. + // At the time of writing, we don't promise ABI compatibility + // so we can change this but I want to be aware of it. + assert(@sizeOf(CValue) == 16); + } + /// Returns the value type for the given key. pub fn Value(comptime key: Key) type { inline for (@typeInfo(Action).@"union".fields) |field| { From f22893395550f82ed80c906b86614f128629eea4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 4 Apr 2025 19:04:33 -0400 Subject: [PATCH 19/73] macos: left mouse click while not focused doesn't encode to pty Fixes #2595 This fixes an issue where a left mouse click on a terminal while not focused would subsequently be encoded to the pty as a mouse event. This is atypical for macOS applications in general and wasn't something we wanted to do. We do, however, want to ensure our terminal gains focus when clicked without focus. Specifically, a split. This matches iTerm2 behavior and is rather nice. We had this behavior before but our logic to make this work before caused the issue this commit is fixing. I also tested this with command+click which is a common macOS shortcut to emit a mouse event without raising the focus of the target window. In this case, we will properly focus the split but will not encode the mouse event to the pty. I think we actually do a _better job_ here tha iTerm2 (but, subjective) because we do encode the pty event properly if the split is focused whereas iTerm2 never does. --- .../Sources/Ghostty/SurfaceView_AppKit.swift | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index f04bf1af3..301ef5a9b 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -201,7 +201,14 @@ extension Ghostty { self.eventMonitor = NSEvent.addLocalMonitorForEvents( matching: [ // We need keyUp because command+key events don't trigger keyUp. - .keyUp + .keyUp, + + // We need leftMouseDown to determine if we should focus ourselves + // when the app/window isn't in focus. We do this instead of + // "acceptsFirstMouse" because that forces us to also handle the + // event and encode the event to the pty which we want to avoid. + // (Issue 2595) + .leftMouseDown, ] ) { [weak self] event in self?.localEventHandler(event) } @@ -450,11 +457,40 @@ extension Ghostty { case .keyUp: localEventKeyUp(event) + case .leftMouseDown: + localEventLeftMouseDown(event) + default: event } } + private func localEventLeftMouseDown(_ event: NSEvent) -> NSEvent? { + // We only want to process events that are on this window. + guard let window, + event.window != nil, + window == event.window else { return event } + + // The clicked location in this window should be this view. + let location = convert(event.locationInWindow, from: nil) + guard hitTest(location) == self else { return event } + + // We only want to grab focus if either our app or window was + // not focused. + guard !NSApp.isActive || !window.isKeyWindow else { return event } + + // If we're already focused we do nothing + guard !focused else { return event } + + // Make ourselves the first responder + window.makeFirstResponder(self) + + // We have to keep processing the event so that AppKit can properly + // focus the window and dispatch events. If you return nil here then + // nobody gets a windowDidBecomeKey event and so on. + return event + } + private func localEventKeyUp(_ event: NSEvent) -> NSEvent? { // We only care about events with "command" because all others will // trigger the normal responder chain. @@ -620,14 +656,6 @@ extension Ghostty { ghostty_surface_draw(surface); } - override func acceptsFirstMouse(for event: NSEvent?) -> Bool { - // "Override this method in a subclass to allow instances to respond to - // click-through. This allows the user to click on a view in an inactive - // window, activating the view with one click, instead of clicking first - // to make the window active and then clicking the view." - return true - } - override func mouseDown(with event: NSEvent) { guard let surface = self.surface else { return } let mods = Ghostty.ghosttyMods(event.modifierFlags) From fe0536aaaf570f6f71f9ba4d476afb89ee90ccbe Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 4 Apr 2025 22:08:06 -0400 Subject: [PATCH 20/73] macos: replay control+key events that go to doCommand Fixes #7000 Related to #6909, the same mechanism, but it turns out some control+keys are also handled in this same way (namely control+esc leads to "cancel" by default, which is not what we want). --- .../Sources/Ghostty/SurfaceView_AppKit.swift | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 301ef5a9b..c6a3d7629 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -915,7 +915,7 @@ extension Ghostty { // If we are in a keyDown then we don't need to redispatch a command-modded // key event (see docs for this field) so reset this to nil because // `interpretKeyEvents` may dispach it. - self.lastCommandEvent = nil + self.lastPerformKeyEvent = nil self.interpretKeyEvents([translationEvent]) @@ -955,7 +955,8 @@ extension Ghostty { _ = keyAction(GHOSTTY_ACTION_RELEASE, event: event) } - /// Records the timestamp of the last event to performKeyEquivalent that had a command key active. + /// Records the timestamp of the last event to performKeyEquivalent that we need to save. + /// We currently save all commands with command or control set. /// /// For command+key inputs, the AppKit input stack calls performKeyEquivalent to give us a chance /// to handle them first. If we return "false" then it goes through the standard AppKit responder chain. @@ -980,7 +981,7 @@ extension Ghostty { /// The best thing I could find was to store the event timestamp which has decent granularity /// and compare that. To further complicate things, some events are synthetic and have a zero /// timestamp so we have to protect against that. Fun! - var lastCommandEvent: TimeInterval? + var lastPerformKeyEvent: TimeInterval? /// Special case handling for some control keys override func performKeyEquivalent(with event: NSEvent) -> Bool { @@ -1053,23 +1054,24 @@ extension Ghostty { // Ignore all other non-command events. This lets the event continue // through the AppKit event systems. - if (!event.modifierFlags.contains(.command)) { + if (!event.modifierFlags.contains(.command) && + !event.modifierFlags.contains(.control)) { // Reset since we got a non-command event. - lastCommandEvent = nil + lastPerformKeyEvent = nil return false } // If we have a prior command binding and the timestamp matches exactly // then we pass it through to keyDown for encoding. - if let lastCommandEvent { - self.lastCommandEvent = nil - if lastCommandEvent == event.timestamp { + if let lastPerformKeyEvent { + self.lastPerformKeyEvent = nil + if lastPerformKeyEvent == event.timestamp { equivalent = event.characters ?? "" break } } - lastCommandEvent = event.timestamp + lastPerformKeyEvent = event.timestamp return false } @@ -1572,9 +1574,9 @@ extension Ghostty.SurfaceView: NSTextInputClient { override func doCommand(by selector: Selector) { // If we are being processed by performKeyEquivalent with a command binding, // we send it back through the event system so it can be encoded. - if let lastCommandEvent, + if let lastPerformKeyEvent, let current = NSApp.currentEvent, - lastCommandEvent == current.timestamp + lastPerformKeyEvent == current.timestamp { NSApp.sendEvent(current) return From f6ec39a5d8a99c9a62957823f2a3c4e81e323062 Mon Sep 17 00:00:00 2001 From: mitchellh <1299+mitchellh@users.noreply.github.com> Date: Sun, 6 Apr 2025 00:13:30 +0000 Subject: [PATCH 21/73] deps: Update iTerm2 color schemes --- build.zig.zon | 4 ++-- build.zig.zon.json | 6 +++--- build.zig.zon.nix | 6 +++--- build.zig.zon.txt | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index fb0b8cb1f..086e19dd8 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -103,8 +103,8 @@ // Other .apple_sdk = .{ .path = "./pkg/apple-sdk" }, .iterm2_themes = .{ - .url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8650079de477e80a5983646e3e4d24cda1dbaefa.tar.gz", - .hash = "N-V-__8AADk6LwSAbK3OMyGiadf6aeyztHNV4-zKaLy6IZa6", + .url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/4c57d8c11d352a4aeda6928b65d78794c28883a5.tar.gz", + .hash = "N-V-__8AAEH8MwQaEsARbyV42-bSZGcu1am8xtg2h67wTFC3", .lazy = true, }, }, diff --git a/build.zig.zon.json b/build.zig.zon.json index c93f16231..d43bf3d56 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -54,10 +54,10 @@ "url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz", "hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA=" }, - "N-V-__8AADk6LwSAbK3OMyGiadf6aeyztHNV4-zKaLy6IZa6": { + "N-V-__8AAEH8MwQaEsARbyV42-bSZGcu1am8xtg2h67wTFC3": { "name": "iterm2_themes", - "url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8650079de477e80a5983646e3e4d24cda1dbaefa.tar.gz", - "hash": "sha256-nOkH31MQQd2PPdjVpRxBxNQWfR9Exg6nRF/KHgSz3cM=" + "url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/4c57d8c11d352a4aeda6928b65d78794c28883a5.tar.gz", + "hash": "sha256-c+twvkEPiz1DaULYlnGXLxis19Q2h+TgBJxoARMasjU=" }, "N-V-__8AAJrvXQCqAT8Mg9o_tk6m0yf5Fz-gCNEOKLyTSerD": { "name": "libpng", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index 407c5da7b..1dc56da50 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -170,11 +170,11 @@ in }; } { - name = "N-V-__8AADk6LwSAbK3OMyGiadf6aeyztHNV4-zKaLy6IZa6"; + name = "N-V-__8AAEH8MwQaEsARbyV42-bSZGcu1am8xtg2h67wTFC3"; path = fetchZigArtifact { name = "iterm2_themes"; - url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8650079de477e80a5983646e3e4d24cda1dbaefa.tar.gz"; - hash = "sha256-nOkH31MQQd2PPdjVpRxBxNQWfR9Exg6nRF/KHgSz3cM="; + url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/4c57d8c11d352a4aeda6928b65d78794c28883a5.tar.gz"; + hash = "sha256-c+twvkEPiz1DaULYlnGXLxis19Q2h+TgBJxoARMasjU="; }; } { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index f2cc560cc..b9bdc50d2 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -27,7 +27,7 @@ https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5. https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst -https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8650079de477e80a5983646e3e4d24cda1dbaefa.tar.gz +https://github.com/mbadolato/iTerm2-Color-Schemes/archive/4c57d8c11d352a4aeda6928b65d78794c28883a5.tar.gz https://github.com/mitchellh/libxev/archive/3df9337a9e84450a58a2c4af434ec1a036f7b494.tar.gz https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz From 9144f4db58befb90b3609b9698f0a41213042e4d Mon Sep 17 00:00:00 2001 From: Bryan Lee <38807139+liby@users.noreply.github.com> Date: Tue, 8 Apr 2025 00:44:53 +0800 Subject: [PATCH 22/73] Fix macOS shortcut binding for `close_window` action --- .../Terminal/TerminalController.swift | 12 +++++++++ macos/Sources/Ghostty/Ghostty.App.swift | 25 ++++++++++++++++++- macos/Sources/Ghostty/Package.swift | 3 +++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index ddc459c5b..f54eb6539 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -91,6 +91,12 @@ class TerminalController: BaseTerminalController { name: Ghostty.Notification.didEqualizeSplits, object: nil ) + center.addObserver( + self, + selector: #selector(onCloseWindow), + name: .ghosttyCloseWindow, + object: nil + ) } required init?(coder: NSCoder) { @@ -842,6 +848,12 @@ class TerminalController: BaseTerminalController { closeTab(self) } + @objc private func onCloseWindow(notification: SwiftUI.Notification) { + guard let target = notification.object as? Ghostty.SurfaceView else { return } + guard surfaceTree?.contains(view: target) ?? false else { return } + closeWindow(self) + } + @objc private func onResetWindowSize(notification: SwiftUI.Notification) { guard let target = notification.object as? Ghostty.SurfaceView else { return } guard surfaceTree?.contains(view: target) ?? false else { return } diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 88f8d1dc9..ddb954e04 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -107,7 +107,7 @@ extension Ghostty { deinit { // This will force the didSet callbacks to run which free. self.app = nil - + #if os(macOS) NotificationCenter.default.removeObserver(self) #endif @@ -451,6 +451,9 @@ extension Ghostty { case GHOSTTY_ACTION_CLOSE_TAB: closeTab(app, target: target) + case GHOSTTY_ACTION_CLOSE_WINDOW: + closeWindow(app, target: target) + case GHOSTTY_ACTION_TOGGLE_FULLSCREEN: toggleFullscreen(app, target: target, mode: action.action.toggle_fullscreen) @@ -686,6 +689,26 @@ extension Ghostty { } } + private static func closeWindow(_ app: ghostty_app_t, target: ghostty_target_s) { + switch (target.tag) { + case GHOSTTY_TARGET_APP: + Ghostty.logger.warning("close window does nothing with an app target") + return + + case GHOSTTY_TARGET_SURFACE: + guard let surface = target.target.surface else { return } + guard let surfaceView = self.surfaceView(from: surface) else { return } + + NotificationCenter.default.post( + name: .ghosttyCloseWindow, + object: surfaceView + ) + + default: + assertionFailure() + } + } + private static func toggleFullscreen( _ app: ghostty_app_t, target: ghostty_target_s, diff --git a/macos/Sources/Ghostty/Package.swift b/macos/Sources/Ghostty/Package.swift index ca37002b0..cda4b557e 100644 --- a/macos/Sources/Ghostty/Package.swift +++ b/macos/Sources/Ghostty/Package.swift @@ -248,6 +248,9 @@ extension Notification.Name { /// Close tab static let ghosttyCloseTab = Notification.Name("com.mitchellh.ghostty.closeTab") + /// Close window + static let ghosttyCloseWindow = Notification.Name("com.mitchellh.ghostty.closeWindow") + /// Resize the window to a default size. static let ghosttyResetWindowSize = Notification.Name("com.mitchellh.ghostty.resetWindowSize") } From df174a74f83f71118e21b740c85ddad84f6b599b Mon Sep 17 00:00:00 2001 From: Fabian Boehm Date: Mon, 7 Apr 2025 21:20:21 +0200 Subject: [PATCH 23/73] shell-integration: Fix condition for sudo A missing ";" meant the check for $TERMINFO was never executed. --- .../fish/vendor_conf.d/ghostty-shell-integration.fish | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish b/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish index 35cea144a..e7c264e1f 100644 --- a/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish +++ b/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish @@ -63,7 +63,7 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration" # When using sudo shell integration feature, ensure $TERMINFO is set # and `sudo` is not already a function or alias - if contains sudo $features and test -n "$TERMINFO"; and test "file" = (type -t sudo 2> /dev/null; or echo "x") + if contains sudo $features; and test -n "$TERMINFO"; and test "file" = (type -t sudo 2> /dev/null; or echo "x") # Wrap `sudo` command to ensure Ghostty terminfo is preserved function sudo -d "Wrap sudo to preserve terminfo" set --function sudo_has_sudoedit_flags "no" From b64f49a0d7de72dffde02369c81cd94052a2e6db Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Mon, 7 Apr 2025 13:31:51 -0600 Subject: [PATCH 24/73] fix(kittygfx): accept commands with no control data This sort of command is treated as valid by Kitty so we should too. In fact, it occurs with the example `send-png` script provided in the docs for the protocol. --- src/terminal/kitty/graphics_command.zig | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/terminal/kitty/graphics_command.zig b/src/terminal/kitty/graphics_command.zig index 840949d74..61ba33a4d 100644 --- a/src/terminal/kitty/graphics_command.zig +++ b/src/terminal/kitty/graphics_command.zig @@ -98,6 +98,12 @@ pub const Parser = struct { self.state = .control_value; }, + // This can be encountered if we have a sequence with no + // control data, only payload data (i.e. "\x1b_G;"). + // + // Kitty treats this as valid so we do as well. + ';' => self.state = .data, + else => try self.accumulateValue(c, .control_key_ignore), }, @@ -1053,6 +1059,21 @@ test "delete command" { try testing.expectEqual(@as(u32, 4), dv.y); } +test "no control data" { + const testing = std.testing; + const alloc = testing.allocator; + var p = Parser.init(alloc); + defer p.deinit(); + + const input = ";QUFBQQ"; + for (input) |c| try p.feed(c); + const command = try p.complete(); + defer command.deinit(alloc); + + try testing.expect(command.control == .transmit); + try testing.expectEqualStrings("AAAA", command.data); +} + test "ignore unknown keys (long)" { const testing = std.testing; const alloc = testing.allocator; From 9808c137969c4f3eaa308c6ae390d76b0fcbebd5 Mon Sep 17 00:00:00 2001 From: Hanna Date: Mon, 7 Apr 2025 16:02:53 -0400 Subject: [PATCH 25/73] refactor: use builtin hostname function --- src/shell-integration/elvish/lib/ghostty-integration.elv | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/shell-integration/elvish/lib/ghostty-integration.elv b/src/shell-integration/elvish/lib/ghostty-integration.elv index b0de19d3f..6c35a21c5 100644 --- a/src/shell-integration/elvish/lib/ghostty-integration.elv +++ b/src/shell-integration/elvish/lib/ghostty-integration.elv @@ -75,7 +75,8 @@ } fn report-pwd { - printf "\e]7;file://%s%s\a" (hostname) (pwd) + use platform + printf "\e]7;file://%s%s\a" (platform:hostname) ($pwd) } fn sudo-with-terminfo {|@args| @@ -109,7 +110,7 @@ var features = [(str:split ',' $E:GHOSTTY_SHELL_FEATURES)] if (has-value $features title) { - set after-chdir = (conj $after-chdir {|_| report-pwd }) + set after-chdir = (conj $after-chdir {|_| report- }) } if (has-value $features cursor) { fn beam { printf "\e[5 q" } From 77f5fe256064f2f9f10123f507d42d4e847cc843 Mon Sep 17 00:00:00 2001 From: Hanna Date: Mon, 7 Apr 2025 16:09:43 -0400 Subject: [PATCH 26/73] fix: parenthesis are unneeded around builtins --- src/shell-integration/elvish/lib/ghostty-integration.elv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shell-integration/elvish/lib/ghostty-integration.elv b/src/shell-integration/elvish/lib/ghostty-integration.elv index 6c35a21c5..319cffa8d 100644 --- a/src/shell-integration/elvish/lib/ghostty-integration.elv +++ b/src/shell-integration/elvish/lib/ghostty-integration.elv @@ -76,7 +76,7 @@ fn report-pwd { use platform - printf "\e]7;file://%s%s\a" (platform:hostname) ($pwd) + printf "\e]7;file://%s%s\a" platform:hostname $pwd } fn sudo-with-terminfo {|@args| From a8f760c6d2a6027d5161cc9ed3cce40526f8446f Mon Sep 17 00:00:00 2001 From: Hanna Date: Mon, 7 Apr 2025 16:10:50 -0400 Subject: [PATCH 27/73] fix: undo accidental replace --- src/shell-integration/elvish/lib/ghostty-integration.elv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shell-integration/elvish/lib/ghostty-integration.elv b/src/shell-integration/elvish/lib/ghostty-integration.elv index 319cffa8d..bf6477aed 100644 --- a/src/shell-integration/elvish/lib/ghostty-integration.elv +++ b/src/shell-integration/elvish/lib/ghostty-integration.elv @@ -110,7 +110,7 @@ var features = [(str:split ',' $E:GHOSTTY_SHELL_FEATURES)] if (has-value $features title) { - set after-chdir = (conj $after-chdir {|_| report- }) + set after-chdir = (conj $after-chdir {|_| report-pwd }) } if (has-value $features cursor) { fn beam { printf "\e[5 q" } From b213c157f079787e197bc3ad236db3616f4680fb Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Tue, 8 Apr 2025 10:38:57 -0400 Subject: [PATCH 28/73] elvish: use kitty-shell-cwd:// to report pwd OSC 7's standard body is a percent-encoded file:// URL. There isn't an easy way for us to percent-encode the path ($pwd) component here without implementing a custom function. Instead, switch to the kitty-shell-cwd:// scheme, which Kitty introduced to ease this implementation challenge in shell scripts. It accepts the path string verbatim, without an encoding. In Ghostty, we accept both the file:// and kitty-shell-cwd:// schemes, and we attempt to URI-decode them both, so in practice this is more about the "correctness" of this protocol than a functional change. It's also possible we might decide to treat these schemes differently in the runtime, like Kitty does. --- src/shell-integration/elvish/lib/ghostty-integration.elv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shell-integration/elvish/lib/ghostty-integration.elv b/src/shell-integration/elvish/lib/ghostty-integration.elv index bf6477aed..be98758c2 100644 --- a/src/shell-integration/elvish/lib/ghostty-integration.elv +++ b/src/shell-integration/elvish/lib/ghostty-integration.elv @@ -76,7 +76,7 @@ fn report-pwd { use platform - printf "\e]7;file://%s%s\a" platform:hostname $pwd + printf "\e]7;kitty-shell-cwd://%s%s\a" platform:hostname $pwd } fn sudo-with-terminfo {|@args| From 5b4976f6ef998948058560676a47bdc519643e0f Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Tue, 8 Apr 2025 10:53:45 -0400 Subject: [PATCH 29/73] elvish: fix platform:hostname function call syntax --- src/shell-integration/elvish/lib/ghostty-integration.elv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shell-integration/elvish/lib/ghostty-integration.elv b/src/shell-integration/elvish/lib/ghostty-integration.elv index be98758c2..a6d052a72 100644 --- a/src/shell-integration/elvish/lib/ghostty-integration.elv +++ b/src/shell-integration/elvish/lib/ghostty-integration.elv @@ -76,7 +76,7 @@ fn report-pwd { use platform - printf "\e]7;kitty-shell-cwd://%s%s\a" platform:hostname $pwd + printf "\e]7;kitty-shell-cwd://%s%s\a" (platform:hostname) $pwd } fn sudo-with-terminfo {|@args| From cb1b447e8cc099aa60bf309df910b3fcce3f4ea6 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Tue, 8 Apr 2025 18:33:12 -0500 Subject: [PATCH 30/73] gtk: fix forcing the window theme to light or dark Fixes #7038 --- src/apprt/gtk/App.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index b4bebe8ee..ddee49459 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -314,8 +314,8 @@ pub fn init(core_app: *CoreApp, opts: Options) !App { .prefer_dark; }, .system => .prefer_light, - .dark => .prefer_dark, - .light => .force_dark, + .dark => .force_dark, + .light => .force_light, }, ); From 722d41a359d71f251efab9135d1bef5837512352 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 5 Apr 2025 11:45:40 -0400 Subject: [PATCH 31/73] config: allow commands to specify whether they shell expand or not This introduces a syntax for `command` and `initial-command` that allows the user to specify whether it should be run via `/bin/sh -c` or not. The syntax is a prefix `direct:` or `shell:` prior to the command, with no prefix implying a default behavior as documented. Previously, we unconditionally ran commands via `/bin/sh -c`, primarily to avoid having to do any shell expansion ourselves. We also leaned on it as a crutch for PATH-expansion but this is an easy problem compared to shell expansion. For the principle of least surprise, this worked well for configurations specified via the config file, and is still the default. However, these configurations are also set via the `-e` special flag to the CLI, and it is very much not the principle of least surprise to have the command run via `/bin/sh -c` in that scenario since a shell has already expanded all the arguments and given them to us in a nice separated format. But we had no way to toggle this behavior. This commit introduces the ability to do this, and changes the defaults so that `-e` doesn't shell expand. Further, we also do PATH lookups ourselves for the non-shell expanded case because thats easy (using execvpe style extensions but implemented as part of the Zig stdlib). We don't do path expansion (e.g. `~/`) because thats a shell expansion. So to be clear, there are no two polar opposite behavioes here with clear semantics: 1. Direct commands are passed to `execvpe` directly, space separated. This will not handle quoted strings, environment variables, path expansion (e.g. `~/`), command expansion (e.g. `$()`), etc. 2. Shell commands are passed to `/bin/sh -c` and will be shell expanded as per the shell's rules. This will handle everything that `sh` supports. In doing this work, I also stumbled upon a variety of smaller improvements that could be made: - A number of allocations have been removed from the startup path that only existed to add a null terminator to various strings. We now have null terminators from the beginning since we are almost always on a system that's going to need it anyways. - For bash shell integration, we no longer wrap the new bash command in a shell since we've formed a full parsed command line. - The process of creating the command to execute by termio is now unit tested, so we can test the various complex cases particularly on macOS of wrapping commands in the login command. - `xdg-terminal-exec` on Linux uses the `direct:` method by default since it is also assumed to be executed via a shell environment. --- src/Command.zig | 20 +- src/Surface.zig | 28 +- src/apprt/embedded.zig | 7 +- src/config.zig | 1 + src/config/Config.zig | 92 ++++-- src/config/command.zig | 322 +++++++++++++++++++ src/os/passwd.zig | 25 +- src/termio/Exec.zig | 517 ++++++++++++++++++++++--------- src/termio/shell_integration.zig | 172 ++++++---- 9 files changed, 901 insertions(+), 283 deletions(-) create mode 100644 src/config/command.zig diff --git a/src/Command.zig b/src/Command.zig index a810b16ce..e17c1b370 100644 --- a/src/Command.zig +++ b/src/Command.zig @@ -33,14 +33,17 @@ const EnvMap = std.process.EnvMap; const PreExecFn = fn (*Command) void; -/// Path to the command to run. This must be an absolute path. This -/// library does not do PATH lookup. -path: []const u8, +/// Path to the command to run. This doesn't have to be an absolute path, +/// because use exec functions that search the PATH, if necessary. +/// +/// This field is null-terminated to avoid a copy for the sake of +/// adding a null terminator since POSIX systems are so common. +path: [:0]const u8, /// Command-line arguments. It is the responsibility of the caller to set /// args[0] to the command. If args is empty then args[0] will automatically /// be set to equal path. -args: []const []const u8, +args: []const [:0]const u8, /// Environment variables for the child process. If this is null, inherits /// the environment variables from this process. These are the exact @@ -129,9 +132,8 @@ pub fn start(self: *Command, alloc: Allocator) !void { fn startPosix(self: *Command, arena: Allocator) !void { // Null-terminate all our arguments - const pathZ = try arena.dupeZ(u8, self.path); - const argsZ = try arena.allocSentinel(?[*:0]u8, self.args.len, null); - for (self.args, 0..) |arg, i| argsZ[i] = (try arena.dupeZ(u8, arg)).ptr; + const argsZ = try arena.allocSentinel(?[*:0]const u8, self.args.len, null); + for (self.args, 0..) |arg, i| argsZ[i] = arg.ptr; // Determine our env vars const envp = if (self.env) |env_map| @@ -184,7 +186,9 @@ fn startPosix(self: *Command, arena: Allocator) !void { if (self.pre_exec) |f| f(self); // Finally, replace our process. - _ = posix.execveZ(pathZ, argsZ, envp) catch null; + // Note: we must use the "p"-variant of exec here because we + // do not guarantee our command is looked up already in the path. + _ = posix.execvpeZ(self.path, argsZ, envp) catch null; // If we are executing this code, the exec failed. In that scenario, // we return a very specific error that can be detected to determine diff --git a/src/Surface.zig b/src/Surface.zig index 46fa476f7..89031a1b5 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -518,7 +518,7 @@ pub fn init( }; // The command we're going to execute - const command: ?[]const u8 = if (app.first) + const command: ?configpkg.Command = if (app.first) config.@"initial-command" orelse config.command else config.command; @@ -650,21 +650,19 @@ pub fn init( // title to the command being executed. This allows window managers // to set custom styling based on the command being executed. const v = command orelse break :xdg; - if (v.len > 0) { - const title = alloc.dupeZ(u8, v) catch |err| { - log.warn( - "error copying command for title, title will not be set err={}", - .{err}, - ); - break :xdg; - }; - defer alloc.free(title); - _ = try rt_app.performAction( - .{ .surface = self }, - .set_title, - .{ .title = title }, + const title = v.string(alloc) catch |err| { + log.warn( + "error copying command for title, title will not be set err={}", + .{err}, ); - } + break :xdg; + }; + defer alloc.free(title); + _ = try rt_app.performAction( + .{ .surface = self }, + .set_title, + .{ .title = title }, + ); } // We are no longer the first surface diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 9ae00ab8e..50b54435d 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -636,6 +636,11 @@ pub const Surface = struct { /// The command to run in the new surface. If this is set then /// the "wait-after-command" option is also automatically set to true, /// since this is used for scripting. + /// + /// This command always run in a shell (e.g. via `/bin/sh -c`), + /// despite Ghostty allowing directly executed commands via config. + /// This is a legacy thing and we should probably change it in the + /// future once we have a concrete use case. command: [*:0]const u8 = "", }; @@ -696,7 +701,7 @@ pub const Surface = struct { // If we have a command from the options then we set it. const cmd = std.mem.sliceTo(opts.command, 0); if (cmd.len > 0) { - config.command = cmd; + config.command = .{ .shell = cmd }; config.@"wait-after-command" = true; } diff --git a/src/config.zig b/src/config.zig index a06e19872..fb7359b3e 100644 --- a/src/config.zig +++ b/src/config.zig @@ -14,6 +14,7 @@ pub const formatEntry = formatter.formatEntry; // Field types pub const ClipboardAccess = Config.ClipboardAccess; +pub const Command = Config.Command; pub const ConfirmCloseSurface = Config.ConfirmCloseSurface; pub const CopyOnSelect = Config.CopyOnSelect; pub const CustomShaderAnimation = Config.CustomShaderAnimation; diff --git a/src/config/Config.zig b/src/config/Config.zig index 9cd285d3b..a0d9275e9 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -22,7 +22,6 @@ const inputpkg = @import("../input.zig"); const terminal = @import("../terminal/main.zig"); const internal_os = @import("../os/main.zig"); const cli = @import("../cli.zig"); -const Command = @import("../Command.zig"); const conditional = @import("conditional.zig"); const Conditional = conditional.Conditional; @@ -34,6 +33,7 @@ const KeyValue = @import("key.zig").Value; const ErrorList = @import("ErrorList.zig"); const MetricModifier = fontpkg.Metrics.Modifier; const help_strings = @import("help_strings"); +pub const Command = @import("command.zig").Command; const RepeatableStringMap = @import("RepeatableStringMap.zig"); pub const Path = @import("path.zig").Path; pub const RepeatablePath = @import("path.zig").RepeatablePath; @@ -691,8 +691,17 @@ palette: Palette = .{}, /// * `passwd` entry (user information) /// /// This can contain additional arguments to run the command with. If additional -/// arguments are provided, the command will be executed using `/bin/sh -c`. -/// Ghostty does not do any shell command parsing. +/// arguments are provided, the command will be executed using `/bin/sh -c` +/// to offload shell argument expansion. +/// +/// To avoid shell expansion altogether, prefix the command with `direct:`, +/// e.g. `direct:nvim foo`. This will avoid the roundtrip to `/bin/sh` but will +/// also not support any shell parsing such as arguments with spaces, filepaths +/// with `~`, globs, etc. +/// +/// You can also explicitly prefix the command with `shell:` to always +/// wrap the command in a shell. This can be used to ensure our heuristics +/// to choose the right mode are not used in case they are wrong. /// /// This command will be used for all new terminal surfaces, i.e. new windows, /// tabs, etc. If you want to run a command only for the first terminal surface @@ -702,7 +711,7 @@ palette: Palette = .{}, /// arguments. For example, `ghostty -e fish --with --custom --args`. /// This flag sets the `initial-command` configuration, see that for more /// information. -command: ?[]const u8 = null, +command: ?Command = null, /// This is the same as "command", but only applies to the first terminal /// surface created when Ghostty starts. Subsequent terminal surfaces will use @@ -718,6 +727,10 @@ command: ?[]const u8 = null, /// fish --with --custom --args`. The `-e` flag automatically forces some /// other behaviors as well: /// +/// * Disables shell expansion since the input is expected to already +/// be shell-expanded by the upstream (e.g. the shell used to type in +/// the `ghostty -e` command). +/// /// * `gtk-single-instance=false` - This ensures that a new instance is /// launched and the CLI args are respected. /// @@ -735,7 +748,7 @@ command: ?[]const u8 = null, /// name your binary appropriately or source the shell integration script /// manually. /// -@"initial-command": ?[]const u8 = null, +@"initial-command": ?Command = null, /// Extra environment variables to pass to commands launched in a terminal /// surface. The format is `env=KEY=VALUE`. @@ -2564,21 +2577,17 @@ pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void { // Next, take all remaining args and use that to build up // a command to execute. - var command = std.ArrayList(u8).init(arena_alloc); - errdefer command.deinit(); + var builder = std.ArrayList([:0]const u8).init(arena_alloc); + errdefer builder.deinit(); for (args) |arg_raw| { const arg = std.mem.sliceTo(arg_raw, 0); - try self._replay_steps.append( - arena_alloc, - .{ .arg = try arena_alloc.dupe(u8, arg) }, - ); - - try command.appendSlice(arg); - try command.append(' '); + const copy = try arena_alloc.dupeZ(u8, arg); + try self._replay_steps.append(arena_alloc, .{ .arg = copy }); + try builder.append(copy); } self.@"_xdg-terminal-exec" = true; - self.@"initial-command" = command.items[0 .. command.items.len - 1]; + self.@"initial-command" = .{ .direct = try builder.toOwnedSlice() }; return; } } @@ -3023,7 +3032,7 @@ pub fn finalize(self: *Config) !void { // We don't do this in flatpak because SHELL in Flatpak is always // set to /bin/sh. if (self.command) |cmd| - log.info("shell src=config value={s}", .{cmd}) + log.info("shell src=config value={}", .{cmd}) else shell_env: { // Flatpak always gets its shell from outside the sandbox if (internal_os.isFlatpak()) break :shell_env; @@ -3035,7 +3044,9 @@ pub fn finalize(self: *Config) !void { if (std.process.getEnvVarOwned(alloc, "SHELL")) |value| { log.info("default shell source=env value={s}", .{value}); - self.command = value; + + const copy = try alloc.dupeZ(u8, value); + self.command = .{ .shell = copy }; // If we don't need the working directory, then we can exit now. if (!wd_home) break :command; @@ -3046,7 +3057,7 @@ pub fn finalize(self: *Config) !void { .windows => { if (self.command == null) { log.warn("no default shell found, will default to using cmd", .{}); - self.command = "cmd.exe"; + self.command = .{ .shell = "cmd.exe" }; } if (wd_home) { @@ -3063,7 +3074,7 @@ pub fn finalize(self: *Config) !void { if (self.command == null) { if (pw.shell) |sh| { log.info("default shell src=passwd value={s}", .{sh}); - self.command = sh; + self.command = .{ .shell = sh }; } } @@ -3145,13 +3156,13 @@ pub fn parseManuallyHook( // Build up the command. We don't clean this up because we take // ownership in our allocator. - var command = std.ArrayList(u8).init(alloc); + var command: std.ArrayList([:0]const u8) = .init(alloc); errdefer command.deinit(); while (iter.next()) |param| { - try self._replay_steps.append(alloc, .{ .arg = try alloc.dupe(u8, param) }); - try command.appendSlice(param); - try command.append(' '); + const copy = try alloc.dupeZ(u8, param); + try self._replay_steps.append(alloc, .{ .arg = copy }); + try command.append(copy); } if (command.items.len == 0) { @@ -3167,9 +3178,8 @@ pub fn parseManuallyHook( return false; } - self.@"initial-command" = command.items[0 .. command.items.len - 1]; - // See "command" docs for the implied configurations and why. + self.@"initial-command" = .{ .direct = command.items }; self.@"gtk-single-instance" = .false; self.@"quit-after-last-window-closed" = true; self.@"quit-after-last-window-closed-delay" = null; @@ -3184,7 +3194,7 @@ pub fn parseManuallyHook( // Keep track of our input args for replay try self._replay_steps.append( alloc, - .{ .arg = try alloc.dupe(u8, arg) }, + .{ .arg = try alloc.dupeZ(u8, arg) }, ); // If we didn't find a special case, continue parsing normally @@ -3377,6 +3387,16 @@ fn equalField(comptime T: type, old: T, new: T) bool { [:0]const u8, => return std.mem.eql(u8, old, new), + []const [:0]const u8, + => { + if (old.len != new.len) return false; + for (old, new) |a, b| { + if (!std.mem.eql(u8, a, b)) return false; + } + + return true; + }, + else => {}, } @@ -3412,6 +3432,8 @@ fn equalField(comptime T: type, old: T, new: T) bool { }, .@"union" => |info| { + if (@hasDecl(T, "equal")) return old.equal(new); + const tag_type = info.tag_type.?; const old_tag = std.meta.activeTag(old); const new_tag = std.meta.activeTag(new); @@ -3441,7 +3463,7 @@ fn equalField(comptime T: type, old: T, new: T) bool { const Replay = struct { const Step = union(enum) { /// An argument to parse as if it came from the CLI or file. - arg: []const u8, + arg: [:0]const u8, /// A base path to expand relative paths against. expand: []const u8, @@ -3481,7 +3503,7 @@ const Replay = struct { return switch (self) { .@"-e" => self, .diagnostic => |v| .{ .diagnostic = try v.clone(alloc) }, - .arg => |v| .{ .arg = try alloc.dupe(u8, v) }, + .arg => |v| .{ .arg = try alloc.dupeZ(u8, v) }, .expand => |v| .{ .expand = try alloc.dupe(u8, v) }, .conditional_arg => |v| conditional: { var conds = try alloc.alloc(Conditional, v.conditions.len); @@ -6620,7 +6642,11 @@ test "parse e: command only" { var it: TestIterator = .{ .data = &.{"foo"} }; try testing.expect(!try cfg.parseManuallyHook(alloc, "-e", &it)); - try testing.expectEqualStrings("foo", cfg.@"initial-command".?); + + const cmd = cfg.@"initial-command".?; + try testing.expect(cmd == .direct); + try testing.expectEqual(cmd.direct.len, 1); + try testing.expectEqualStrings(cmd.direct[0], "foo"); } test "parse e: command and args" { @@ -6631,7 +6657,13 @@ test "parse e: command and args" { var it: TestIterator = .{ .data = &.{ "echo", "foo", "bar baz" } }; try testing.expect(!try cfg.parseManuallyHook(alloc, "-e", &it)); - try testing.expectEqualStrings("echo foo bar baz", cfg.@"initial-command".?); + + const cmd = cfg.@"initial-command".?; + try testing.expect(cmd == .direct); + try testing.expectEqual(cmd.direct.len, 3); + try testing.expectEqualStrings(cmd.direct[0], "echo"); + try testing.expectEqualStrings(cmd.direct[1], "foo"); + try testing.expectEqualStrings(cmd.direct[2], "bar baz"); } test "clone default" { diff --git a/src/config/command.zig b/src/config/command.zig new file mode 100644 index 000000000..9efeb199e --- /dev/null +++ b/src/config/command.zig @@ -0,0 +1,322 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const Allocator = std.mem.Allocator; +const ArenaAllocator = std.heap.ArenaAllocator; +const formatterpkg = @import("formatter.zig"); + +/// A command to execute (argv0 and args). +/// +/// A command is specified as a simple string such as "nvim a b c". +/// By default, we expect the downstream to do some sort of shell expansion +/// on this string. +/// +/// If a command is already expanded and the user does NOT want to do +/// shell expansion (because this usually requires a round trip into +/// /bin/sh or equivalent), specify a `direct:`-prefix. e.g. +/// `direct:nvim a b c`. +/// +/// The whitespace before or around the prefix is ignored. For example, +/// ` direct:nvim a b c` and `direct: nvim a b c` are equivalent. +/// +/// If the command is not absolute, it'll be looked up via the PATH. +/// For the shell-expansion case, we let the shell do this. For the +/// direct case, we do this directly. +pub const Command = union(enum) { + const Self = @This(); + + /// Execute a command directly, e.g. via `exec`. The format here + /// is already structured to be ready to passed directly to `exec` + /// with index zero being the command to execute. + /// + /// Index zero is not guaranteed to be an absolute path, and may require + /// PATH lookup. It is up to the downstream to do this, usually via + /// delegation to something like `execvp`. + direct: []const [:0]const u8, + + /// Execute a command via shell expansion. This provides the command + /// as a single string that is expected to be expanded in some way + /// (up to the downstream). Usually `/bin/sh -c`. + shell: [:0]const u8, + + pub fn parseCLI( + self: *Self, + alloc: Allocator, + input_: ?[]const u8, + ) !void { + // Input is required. Whitespace on the edges isn't needed. + // Commands must be non-empty. + const input = input_ orelse return error.ValueRequired; + const trimmed = std.mem.trim(u8, input, " "); + if (trimmed.len == 0) return error.ValueRequired; + + // If we have a `:` then we MIGHT have a prefix to specify what + // tag we should use. + const tag: std.meta.Tag(Self), const str: []const u8 = tag: { + if (std.mem.indexOfScalar(u8, trimmed, ':')) |idx| { + const prefix = trimmed[0..idx]; + if (std.mem.eql(u8, prefix, "direct")) { + break :tag .{ .direct, trimmed[idx + 1 ..] }; + } else if (std.mem.eql(u8, prefix, "shell")) { + break :tag .{ .shell, trimmed[idx + 1 ..] }; + } + } + + break :tag .{ .shell, trimmed }; + }; + + switch (tag) { + .shell => { + // We have a shell command, so we can just dupe it. + const copy = try alloc.dupeZ(u8, std.mem.trim(u8, str, " ")); + self.* = .{ .shell = copy }; + }, + + .direct => { + // We're not shell expanding, so the arguments are naively + // split on spaces. + var builder: std.ArrayListUnmanaged([:0]const u8) = .empty; + var args = std.mem.splitScalar( + u8, + std.mem.trim(u8, str, " "), + ' ', + ); + while (args.next()) |arg| { + const copy = try alloc.dupeZ(u8, arg); + try builder.append(alloc, copy); + } + + self.* = .{ .direct = try builder.toOwnedSlice(alloc) }; + }, + } + } + + /// Creates a command as a single string, joining arguments as + /// necessary with spaces. Its not guaranteed that this is a valid + /// command; it is only meant to be human readable. + pub fn string( + self: *const Self, + alloc: Allocator, + ) Allocator.Error![:0]const u8 { + return switch (self.*) { + .shell => |v| try alloc.dupeZ(u8, v), + .direct => |v| try std.mem.joinZ(alloc, " ", v), + }; + } + + /// Get an iterator over the arguments array. This may allocate + /// depending on the active tag of the command. + /// + /// For direct commands, this is very cheap and just iterates over + /// the array. There is no allocation. + /// + /// For shell commands, this will use Zig's ArgIteratorGeneral as + /// a best effort shell string parser. This is not guaranteed to be + /// 100% accurate, but it works for common cases. This requires allocation. + pub fn argIterator( + self: *const Self, + alloc: Allocator, + ) Allocator.Error!ArgIterator { + return switch (self.*) { + .direct => |v| .{ .direct = .{ .args = v } }, + .shell => |v| .{ .shell = try .init(alloc, v) }, + }; + } + + /// Iterates over each argument in the command. + pub const ArgIterator = union(enum) { + shell: std.process.ArgIteratorGeneral(.{}), + direct: struct { + i: usize = 0, + args: []const [:0]const u8, + }, + + /// Return the next argument. This may or may not be a copy + /// depending on the active tag. If you want to ensure that every + /// argument is a copy, use the `clone` method first. + pub fn next(self: *ArgIterator) ?[:0]const u8 { + return switch (self.*) { + .shell => |*v| v.next(), + .direct => |*v| { + if (v.i >= v.args.len) return null; + defer v.i += 1; + return v.args[v.i]; + }, + }; + } + + pub fn deinit(self: *ArgIterator) void { + switch (self.*) { + .shell => |*v| v.deinit(), + .direct => {}, + } + } + }; + + pub fn clone( + self: *const Self, + alloc: Allocator, + ) Allocator.Error!Self { + return switch (self.*) { + .shell => |v| .{ .shell = try alloc.dupeZ(u8, v) }, + .direct => |v| direct: { + const copy = try alloc.alloc([:0]const u8, v.len); + for (v, 0..) |arg, i| copy[i] = try alloc.dupeZ(u8, arg); + break :direct .{ .direct = copy }; + }, + }; + } + + pub fn formatEntry(self: Self, formatter: anytype) !void { + switch (self) { + .shell => |v| try formatter.formatEntry([]const u8, v), + + .direct => |v| { + var buf: [4096]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buf); + const writer = fbs.writer(); + writer.writeAll("direct:") catch return error.OutOfMemory; + for (v) |arg| { + writer.writeAll(arg) catch return error.OutOfMemory; + writer.writeByte(' ') catch return error.OutOfMemory; + } + + const written = fbs.getWritten(); + try formatter.formatEntry( + []const u8, + written[0..@intCast(written.len - 1)], + ); + }, + } + } + + test "Command: parseCLI errors" { + const testing = std.testing; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + var v: Self = undefined; + try testing.expectError(error.ValueRequired, v.parseCLI(alloc, null)); + try testing.expectError(error.ValueRequired, v.parseCLI(alloc, "")); + try testing.expectError(error.ValueRequired, v.parseCLI(alloc, " ")); + } + + test "Command: parseCLI shell expanded" { + const testing = std.testing; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + var v: Self = undefined; + try v.parseCLI(alloc, "echo hello"); + try testing.expect(v == .shell); + try testing.expectEqualStrings(v.shell, "echo hello"); + + // Spaces are stripped + try v.parseCLI(alloc, " echo hello "); + try testing.expect(v == .shell); + try testing.expectEqualStrings(v.shell, "echo hello"); + } + + test "Command: parseCLI direct" { + const testing = std.testing; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + var v: Self = undefined; + try v.parseCLI(alloc, "direct:echo hello"); + try testing.expect(v == .direct); + try testing.expectEqual(v.direct.len, 2); + try testing.expectEqualStrings(v.direct[0], "echo"); + try testing.expectEqualStrings(v.direct[1], "hello"); + + // Spaces around the prefix + try v.parseCLI(alloc, " direct: echo hello"); + try testing.expect(v == .direct); + try testing.expectEqual(v.direct.len, 2); + try testing.expectEqualStrings(v.direct[0], "echo"); + try testing.expectEqualStrings(v.direct[1], "hello"); + } + + test "Command: argIterator shell" { + const testing = std.testing; + const alloc = testing.allocator; + + var v: Self = .{ .shell = "echo hello world" }; + var it = try v.argIterator(alloc); + defer it.deinit(); + + try testing.expectEqualStrings(it.next().?, "echo"); + try testing.expectEqualStrings(it.next().?, "hello"); + try testing.expectEqualStrings(it.next().?, "world"); + try testing.expect(it.next() == null); + } + + test "Command: argIterator direct" { + const testing = std.testing; + const alloc = testing.allocator; + + var v: Self = .{ .direct = &.{ "echo", "hello world" } }; + var it = try v.argIterator(alloc); + defer it.deinit(); + + try testing.expectEqualStrings(it.next().?, "echo"); + try testing.expectEqualStrings(it.next().?, "hello world"); + try testing.expect(it.next() == null); + } + + test "Command: string shell" { + const testing = std.testing; + const alloc = testing.allocator; + + var v: Self = .{ .shell = "echo hello world" }; + const str = try v.string(alloc); + defer alloc.free(str); + try testing.expectEqualStrings(str, "echo hello world"); + } + + test "Command: string direct" { + const testing = std.testing; + const alloc = testing.allocator; + + var v: Self = .{ .direct = &.{ "echo", "hello world" } }; + const str = try v.string(alloc); + defer alloc.free(str); + try testing.expectEqualStrings(str, "echo hello world"); + } + + test "Command: formatConfig shell" { + const testing = std.testing; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + var buf = std.ArrayList(u8).init(alloc); + defer buf.deinit(); + + var v: Self = undefined; + try v.parseCLI(alloc, "echo hello"); + try v.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); + try std.testing.expectEqualSlices(u8, "a = echo hello\n", buf.items); + } + + test "Command: formatConfig direct" { + const testing = std.testing; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + var buf = std.ArrayList(u8).init(alloc); + defer buf.deinit(); + + var v: Self = undefined; + try v.parseCLI(alloc, "direct: echo hello"); + try v.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); + try std.testing.expectEqualSlices(u8, "a = direct:echo hello\n", buf.items); + } +}; + +test { + _ = Command; +} diff --git a/src/os/passwd.zig b/src/os/passwd.zig index c12214ee4..e9bbff066 100644 --- a/src/os/passwd.zig +++ b/src/os/passwd.zig @@ -25,9 +25,9 @@ const c = if (builtin.os.tag != .windows) @cImport({ // Entry that is retrieved from the passwd API. This only contains the fields // we care about. pub const Entry = struct { - shell: ?[]const u8 = null, - home: ?[]const u8 = null, - name: ?[]const u8 = null, + shell: ?[:0]const u8 = null, + home: ?[:0]const u8 = null, + name: ?[:0]const u8 = null, }; /// Get the passwd entry for the currently executing user. @@ -117,30 +117,27 @@ pub fn get(alloc: Allocator) !Entry { // Shell and home are the last two entries var it = std.mem.splitBackwardsScalar(u8, std.mem.trimRight(u8, output, " \r\n"), ':'); - result.shell = it.next() orelse null; - result.home = it.next() orelse null; + result.shell = if (it.next()) |v| try alloc.dupeZ(u8, v) else null; + result.home = if (it.next()) |v| try alloc.dupeZ(u8, v) else null; return result; } if (pw.pw_shell) |ptr| { const source = std.mem.sliceTo(ptr, 0); - const sh = try alloc.alloc(u8, source.len); - @memcpy(sh, source); - result.shell = sh; + const value = try alloc.dupeZ(u8, source); + result.shell = value; } if (pw.pw_dir) |ptr| { const source = std.mem.sliceTo(ptr, 0); - const dir = try alloc.alloc(u8, source.len); - @memcpy(dir, source); - result.home = dir; + const value = try alloc.dupeZ(u8, source); + result.home = value; } if (pw.pw_name) |ptr| { const source = std.mem.sliceTo(ptr, 0); - const name = try alloc.alloc(u8, source.len); - @memcpy(name, source); - result.name = name; + const value = try alloc.dupeZ(u8, source); + result.name = value; } return result; diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 61b501258..abe49a47b 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -24,6 +24,7 @@ const SegmentedPool = @import("../datastruct/main.zig").SegmentedPool; const ptypkg = @import("../pty.zig"); const Pty = ptypkg.Pty; const EnvMap = std.process.EnvMap; +const PasswdEntry = internal_os.passwd.Entry; const windows = internal_os.windows; const log = std.log.scoped(.io_exec); @@ -725,7 +726,7 @@ pub const ThreadData = struct { }; pub const Config = struct { - command: ?[]const u8 = null, + command: ?configpkg.Command = null, env: EnvMap, env_override: configpkg.RepeatableStringMap = .{}, shell_integration: configpkg.Config.ShellIntegration = .detect, @@ -746,7 +747,7 @@ const Subprocess = struct { arena: std.heap.ArenaAllocator, cwd: ?[]const u8, env: ?EnvMap, - args: [][]const u8, + args: []const [:0]const u8, grid_size: renderer.GridSize, screen_size: renderer.ScreenSize, pty: ?Pty = null, @@ -892,18 +893,29 @@ const Subprocess = struct { env.remove("VTE_VERSION"); // Setup our shell integration, if we can. - const integrated_shell: ?shell_integration.Shell, const shell_command: []const u8 = shell: { - const default_shell_command = cfg.command orelse switch (builtin.os.tag) { - .windows => "cmd.exe", - else => "sh", - }; + const shell_command: configpkg.Command = shell: { + const default_shell_command: configpkg.Command = + cfg.command orelse .{ .shell = switch (builtin.os.tag) { + .windows => "cmd.exe", + else => "sh", + } }; const force: ?shell_integration.Shell = switch (cfg.shell_integration) { .none => { - // Even if shell integration is none, we still want to set up the feature env vars - try shell_integration.setupFeatures(&env, cfg.shell_integration_features); - break :shell .{ null, default_shell_command }; + // Even if shell integration is none, we still want to + // set up the feature env vars + try shell_integration.setupFeatures( + &env, + cfg.shell_integration_features, + ); + + // This is a source of confusion for users despite being + // opt-in since it results in some Ghostty features not + // working. We always want to log it. + log.info("shell integration disabled by configuration", .{}); + break :shell default_shell_command; }, + .detect => null, .bash => .bash, .elvish => .elvish, @@ -911,9 +923,9 @@ const Subprocess = struct { .zsh => .zsh, }; - const dir = cfg.resources_dir orelse break :shell .{ - null, - default_shell_command, + const dir = cfg.resources_dir orelse { + log.warn("no resources dir set, shell integration disabled", .{}); + break :shell default_shell_command; }; const integration = try shell_integration.setup( @@ -923,19 +935,18 @@ const Subprocess = struct { &env, force, cfg.shell_integration_features, - ) orelse break :shell .{ null, default_shell_command }; + ) orelse { + log.warn("shell could not be detected, no automatic shell integration will be injected", .{}); + break :shell default_shell_command; + }; - break :shell .{ integration.shell, integration.command }; - }; - - if (integrated_shell) |shell| { log.info( "shell integration automatically injected shell={}", - .{shell}, + .{integration.shell}, ); - } else if (cfg.shell_integration != .none) { - log.warn("shell could not be detected, no automatic shell integration will be injected", .{}); - } + + break :shell integration.command; + }; // Add the environment variables that override any others. { @@ -947,134 +958,29 @@ const Subprocess = struct { } // Build our args list - const args = args: { - const cap = 9; // the most we'll ever use - var args = try std.ArrayList([]const u8).initCapacity(alloc, cap); - defer args.deinit(); + const args: []const [:0]const u8 = execCommand( + alloc, + shell_command, + internal_os.passwd, + ) catch |err| switch (err) { + // If we fail to allocate space for the command we want to + // execute, we'd still like to try to run something so + // Ghostty can launch (and maybe the user can debug this further). + // Realistically, if you're getting OOM, I think other stuff is + // about to crash, but we can try. + error.OutOfMemory => oom: { + log.warn("failed to allocate space for command args, falling back to basic shell", .{}); - // If we're on macOS, we have to use `login(1)` to get all of - // the proper environment variables set, a login shell, and proper - // hushlogin behavior. - if (comptime builtin.target.os.tag.isDarwin()) darwin: { - const passwd = internal_os.passwd.get(alloc) catch |err| { - log.warn("failed to read passwd, not using a login shell err={}", .{err}); - break :darwin; + // The comptime here is important to ensure the full slice + // is put into the binary data and not the stack. + break :oom comptime switch (builtin.os.tag) { + .windows => &.{"cmd.exe"}, + else => &.{"/bin/sh"}, }; + }, - const username = passwd.name orelse { - log.warn("failed to get username, not using a login shell", .{}); - break :darwin; - }; - - const hush = if (passwd.home) |home| hush: { - var dir = std.fs.openDirAbsolute(home, .{}) catch |err| { - log.warn( - "failed to open home dir, not checking for hushlogin err={}", - .{err}, - ); - break :hush false; - }; - defer dir.close(); - - break :hush if (dir.access(".hushlogin", .{})) true else |_| false; - } else false; - - const cmd = try std.fmt.allocPrint( - alloc, - "exec -l {s}", - .{shell_command}, - ); - - // The reason for executing login this way is unclear. This - // comment will attempt to explain but prepare for a truly - // unhinged reality. - // - // The first major issue is that on macOS, a lot of users - // put shell configurations in ~/.bash_profile instead of - // ~/.bashrc (or equivalent for another shell). This file is only - // loaded for a login shell so macOS users expect all their terminals - // to be login shells. No other platform behaves this way and its - // totally braindead but somehow the entire dev community on - // macOS has cargo culted their way to this reality so we have to - // do it... - // - // To get a login shell, you COULD just prepend argv0 with a `-` - // but that doesn't fully work because `getlogin()` C API will - // return the wrong value, SHELL won't be set, and various - // other login behaviors that macOS users expect. - // - // The proper way is to use `login(1)`. But login(1) forces - // the working directory to change to the home directory, - // which we may not want. If we specify "-l" then we can avoid - // this behavior but now the shell isn't a login shell. - // - // There is another issue: `login(1)` on macOS 14.3 and earlier - // checked for ".hushlogin" in the working directory. This means - // that if we specify "-l" then we won't get hushlogin honored - // if its in the home directory (which is standard). To get - // around this, we check for hushlogin ourselves and if present - // specify the "-q" flag to login(1). - // - // So to get all the behaviors we want, we specify "-l" but - // execute "bash" (which is built-in to macOS). We then use - // the bash builtin "exec" to replace the process with a login - // shell ("-l" on exec) with the command we really want. - // - // We use "bash" instead of other shells that ship with macOS - // because as of macOS Sonoma, we found with a microbenchmark - // that bash can `exec` into the desired command ~2x faster - // than zsh. - // - // To figure out a lot of this logic I read the login.c - // source code in the OSS distribution Apple provides for - // macOS. - // - // Awesome. - try args.append("/usr/bin/login"); - if (hush) try args.append("-q"); - try args.append("-flp"); - - // We execute bash with "--noprofile --norc" so that it doesn't - // load startup files so that (1) our shell integration doesn't - // break and (2) user configuration doesn't mess this process - // up. - try args.append(username); - try args.append("/bin/bash"); - try args.append("--noprofile"); - try args.append("--norc"); - try args.append("-c"); - try args.append(cmd); - break :args try args.toOwnedSlice(); - } - - if (comptime builtin.os.tag == .windows) { - // We run our shell wrapped in `cmd.exe` so that we don't have - // to parse the command line ourselves if it has arguments. - - // Note we don't free any of the memory below since it is - // allocated in the arena. - const windir = try std.process.getEnvVarOwned(alloc, "WINDIR"); - const cmd = try std.fs.path.join(alloc, &[_][]const u8{ - windir, - "System32", - "cmd.exe", - }); - - try args.append(cmd); - try args.append("/C"); - } else { - // We run our shell wrapped in `/bin/sh` so that we don't have - // to parse the command line ourselves if it has arguments. - // Additionally, some environments (NixOS, I found) use /bin/sh - // to setup some environment variables that are important to - // have set. - try args.append("/bin/sh"); - if (internal_os.isFlatpak()) try args.append("-l"); - try args.append("-c"); - } - - try args.append(shell_command); - break :args try args.toOwnedSlice(); + // This logs on its own, this is a bad error. + error.SystemError => return err, }; // We have to copy the cwd because there is no guarantee that @@ -1562,3 +1468,320 @@ pub const ReadThread = struct { } } }; + +/// Builds the argv array for the process we should exec for the +/// configured command. This isn't as straightforward as it seems since +/// we deal with shell-wrapping, macOS login shells, etc. +/// +/// The passwdpkg comptime argument is expected to have a single function +/// `get(Allocator)` that returns a passwd entry. This is used by macOS +/// to determine the username and home directory for the login shell. +/// It is unused on other platforms. +/// +/// Memory ownership: +/// +/// The allocator should be an arena, since the returned value may or +/// may not be allocated and args may or may not be allocated (or copied). +/// Pointers in the return value may point to pointers in the command +/// struct. +fn execCommand( + alloc: Allocator, + command: configpkg.Command, + comptime passwdpkg: type, +) (Allocator.Error || error{SystemError})![]const [:0]const u8 { + // If we're on macOS, we have to use `login(1)` to get all of + // the proper environment variables set, a login shell, and proper + // hushlogin behavior. + if (comptime builtin.target.os.tag.isDarwin()) darwin: { + const passwd = passwdpkg.get(alloc) catch |err| { + log.warn("failed to read passwd, not using a login shell err={}", .{err}); + break :darwin; + }; + + const username = passwd.name orelse { + log.warn("failed to get username, not using a login shell", .{}); + break :darwin; + }; + + const hush = if (passwd.home) |home| hush: { + var dir = std.fs.openDirAbsolute(home, .{}) catch |err| { + log.warn( + "failed to open home dir, not checking for hushlogin err={}", + .{err}, + ); + break :hush false; + }; + defer dir.close(); + + break :hush if (dir.access(".hushlogin", .{})) true else |_| false; + } else false; + + // If we made it this far we're going to start building + // the actual command. + var args: std.ArrayList([:0]const u8) = try .initCapacity( + alloc, + + // This capacity is chosen based on what we'd need to + // execute a shell command (very common). We can/will + // grow if necessary for a longer command (uncommon). + 9, + ); + defer args.deinit(); + + // The reason for executing login this way is unclear. This + // comment will attempt to explain but prepare for a truly + // unhinged reality. + // + // The first major issue is that on macOS, a lot of users + // put shell configurations in ~/.bash_profile instead of + // ~/.bashrc (or equivalent for another shell). This file is only + // loaded for a login shell so macOS users expect all their terminals + // to be login shells. No other platform behaves this way and its + // totally braindead but somehow the entire dev community on + // macOS has cargo culted their way to this reality so we have to + // do it... + // + // To get a login shell, you COULD just prepend argv0 with a `-` + // but that doesn't fully work because `getlogin()` C API will + // return the wrong value, SHELL won't be set, and various + // other login behaviors that macOS users expect. + // + // The proper way is to use `login(1)`. But login(1) forces + // the working directory to change to the home directory, + // which we may not want. If we specify "-l" then we can avoid + // this behavior but now the shell isn't a login shell. + // + // There is another issue: `login(1)` on macOS 14.3 and earlier + // checked for ".hushlogin" in the working directory. This means + // that if we specify "-l" then we won't get hushlogin honored + // if its in the home directory (which is standard). To get + // around this, we check for hushlogin ourselves and if present + // specify the "-q" flag to login(1). + // + // So to get all the behaviors we want, we specify "-l" but + // execute "bash" (which is built-in to macOS). We then use + // the bash builtin "exec" to replace the process with a login + // shell ("-l" on exec) with the command we really want. + // + // We use "bash" instead of other shells that ship with macOS + // because as of macOS Sonoma, we found with a microbenchmark + // that bash can `exec` into the desired command ~2x faster + // than zsh. + // + // To figure out a lot of this logic I read the login.c + // source code in the OSS distribution Apple provides for + // macOS. + // + // Awesome. + try args.append("/usr/bin/login"); + if (hush) try args.append("-q"); + try args.append("-flp"); + try args.append(username); + + switch (command) { + // Direct args can be passed directly to login, since + // login uses execvp we don't need to worry about PATH + // searching. + .direct => |v| try args.appendSlice(v), + + .shell => |v| { + // Use "exec" to replace the bash process with + // our intended command so we don't have a parent + // process hanging around. + const cmd = try std.fmt.allocPrintZ( + alloc, + "exec -l {s}", + .{v}, + ); + + // We execute bash with "--noprofile --norc" so that it doesn't + // load startup files so that (1) our shell integration doesn't + // break and (2) user configuration doesn't mess this process + // up. + try args.append("/bin/bash"); + try args.append("--noprofile"); + try args.append("--norc"); + try args.append("-c"); + try args.append(cmd); + }, + } + + return try args.toOwnedSlice(); + } + + return switch (command) { + .direct => |v| v, + + .shell => |v| shell: { + var args: std.ArrayList([:0]const u8) = try .initCapacity(alloc, 4); + defer args.deinit(); + + if (comptime builtin.os.tag == .windows) { + // We run our shell wrapped in `cmd.exe` so that we don't have + // to parse the command line ourselves if it has arguments. + + // Note we don't free any of the memory below since it is + // allocated in the arena. + const windir = std.process.getEnvVarOwned( + alloc, + "WINDIR", + ) catch |err| { + log.warn("failed to get WINDIR, cannot run shell command err={}", .{err}); + return error.SystemError; + }; + const cmd = try std.fs.path.joinZ(alloc, &[_][]const u8{ + windir, + "System32", + "cmd.exe", + }); + + try args.append(cmd); + try args.append("/C"); + } else { + // We run our shell wrapped in `/bin/sh` so that we don't have + // to parse the command line ourselves if it has arguments. + // Additionally, some environments (NixOS, I found) use /bin/sh + // to setup some environment variables that are important to + // have set. + try args.append("/bin/sh"); + if (internal_os.isFlatpak()) try args.append("-l"); + try args.append("-c"); + } + + try args.append(v); + break :shell try args.toOwnedSlice(); + }, + }; +} + +test "execCommand darwin: shell command" { + if (comptime !builtin.os.tag.isDarwin()) return error.SkipZigTest; + + const testing = std.testing; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const result = try execCommand(alloc, .{ .shell = "foo bar baz" }, struct { + fn get(_: Allocator) !PasswdEntry { + return .{ + .name = "testuser", + }; + } + }); + + try testing.expectEqual(8, result.len); + try testing.expectEqualStrings(result[0], "/usr/bin/login"); + try testing.expectEqualStrings(result[1], "-flp"); + try testing.expectEqualStrings(result[2], "testuser"); + try testing.expectEqualStrings(result[3], "/bin/bash"); + try testing.expectEqualStrings(result[4], "--noprofile"); + try testing.expectEqualStrings(result[5], "--norc"); + try testing.expectEqualStrings(result[6], "-c"); + try testing.expectEqualStrings(result[7], "exec -l foo bar baz"); +} + +test "execCommand darwin: direct command" { + if (comptime !builtin.os.tag.isDarwin()) return error.SkipZigTest; + + const testing = std.testing; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const result = try execCommand(alloc, .{ .direct = &.{ + "foo", + "bar baz", + } }, struct { + fn get(_: Allocator) !PasswdEntry { + return .{ + .name = "testuser", + }; + } + }); + + try testing.expectEqual(5, result.len); + try testing.expectEqualStrings(result[0], "/usr/bin/login"); + try testing.expectEqualStrings(result[1], "-flp"); + try testing.expectEqualStrings(result[2], "testuser"); + try testing.expectEqualStrings(result[3], "foo"); + try testing.expectEqualStrings(result[4], "bar baz"); +} + +test "execCommand: shell command, empty passwd" { + if (comptime builtin.os.tag == .windows) return error.SkipZigTest; + + const testing = std.testing; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const result = try execCommand( + alloc, + .{ .shell = "foo bar baz" }, + struct { + fn get(_: Allocator) !PasswdEntry { + // Empty passwd entry means we can't construct a macOS + // login command and falls back to POSIX behavior. + return .{}; + } + }, + ); + + try testing.expectEqual(3, result.len); + try testing.expectEqualStrings(result[0], "/bin/sh"); + try testing.expectEqualStrings(result[1], "-c"); + try testing.expectEqualStrings(result[2], "foo bar baz"); +} + +test "execCommand: shell command, error passwd" { + if (comptime builtin.os.tag == .windows) return error.SkipZigTest; + + const testing = std.testing; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const result = try execCommand( + alloc, + .{ .shell = "foo bar baz" }, + struct { + fn get(_: Allocator) !PasswdEntry { + // Failed passwd entry means we can't construct a macOS + // login command and falls back to POSIX behavior. + return error.Fail; + } + }, + ); + + try testing.expectEqual(3, result.len); + try testing.expectEqualStrings(result[0], "/bin/sh"); + try testing.expectEqualStrings(result[1], "-c"); + try testing.expectEqualStrings(result[2], "foo bar baz"); +} + +test "execCommand: direct command, error passwd" { + if (comptime builtin.os.tag == .windows) return error.SkipZigTest; + + const testing = std.testing; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const result = try execCommand(alloc, .{ + .direct = &.{ + "foo", + "bar baz", + }, + }, struct { + fn get(_: Allocator) !PasswdEntry { + // Failed passwd entry means we can't construct a macOS + // login command and falls back to POSIX behavior. + return error.Fail; + } + }); + + try testing.expectEqual(2, result.len); + try testing.expectEqualStrings(result[0], "foo"); + try testing.expectEqualStrings(result[1], "bar baz"); +} diff --git a/src/termio/shell_integration.zig b/src/termio/shell_integration.zig index ae8d5b67c..2cf809694 100644 --- a/src/termio/shell_integration.zig +++ b/src/termio/shell_integration.zig @@ -27,7 +27,7 @@ pub const ShellIntegration = struct { /// bash in particular it may be different. /// /// The memory is allocated in the arena given to setup. - command: []const u8, + command: config.Command, }; /// Set up the command execution environment for automatic @@ -41,7 +41,7 @@ pub const ShellIntegration = struct { pub fn setup( alloc_arena: Allocator, resource_dir: []const u8, - command: []const u8, + command: config.Command, env: *EnvMap, force_shell: ?Shell, features: config.ShellIntegrationFeatures, @@ -51,14 +51,24 @@ pub fn setup( .elvish => "elvish", .fish => "fish", .zsh => "zsh", - } else exe: { - // The command can include arguments. Look for the first space - // and use the basename of the first part as the command's exe. - const idx = std.mem.indexOfScalar(u8, command, ' ') orelse command.len; - break :exe std.fs.path.basename(command[0..idx]); + } else switch (command) { + .direct => |v| std.fs.path.basename(v[0]), + .shell => |v| exe: { + // Shell strings can include spaces so we want to only + // look up to the space if it exists. No shell that we integrate + // has spaces. + const idx = std.mem.indexOfScalar(u8, v, ' ') orelse v.len; + break :exe std.fs.path.basename(v[0..idx]); + }, }; - const result = try setupShell(alloc_arena, resource_dir, command, env, exe); + const result = try setupShell( + alloc_arena, + resource_dir, + command, + env, + exe, + ); // Setup our feature env vars try setupFeatures(env, features); @@ -69,7 +79,7 @@ pub fn setup( fn setupShell( alloc_arena: Allocator, resource_dir: []const u8, - command: []const u8, + command: config.Command, env: *EnvMap, exe: []const u8, ) !?ShellIntegration { @@ -83,7 +93,10 @@ fn setupShell( // we're using Apple's Bash because /bin is non-writable // on modern macOS due to System Integrity Protection. if (comptime builtin.target.os.tag.isDarwin()) { - if (std.mem.eql(u8, "/bin/bash", command)) { + if (std.mem.eql(u8, "/bin/bash", switch (command) { + .direct => |v| v[0], + .shell => |v| v, + })) { return null; } } @@ -104,7 +117,7 @@ fn setupShell( try setupXdgDataDirs(alloc_arena, resource_dir, env); return .{ .shell = .elvish, - .command = try alloc_arena.dupe(u8, command), + .command = try command.clone(alloc_arena), }; } @@ -112,7 +125,7 @@ fn setupShell( try setupXdgDataDirs(alloc_arena, resource_dir, env); return .{ .shell = .fish, - .command = try alloc_arena.dupe(u8, command), + .command = try command.clone(alloc_arena), }; } @@ -120,7 +133,7 @@ fn setupShell( try setupZsh(resource_dir, env); return .{ .shell = .zsh, - .command = try alloc_arena.dupe(u8, command), + .command = try command.clone(alloc_arena), }; } @@ -139,7 +152,14 @@ test "force shell" { inline for (@typeInfo(Shell).@"enum".fields) |field| { const shell = @field(Shell, field.name); - const result = try setup(alloc, ".", "sh", &env, shell, .{}); + const result = try setup( + alloc, + ".", + .{ .shell = "sh" }, + &env, + shell, + .{}, + ); try testing.expectEqual(shell, result.?.shell); } } @@ -215,25 +235,21 @@ test "setup features" { /// enables the integration or null if integration failed. fn setupBash( alloc: Allocator, - command: []const u8, + command: config.Command, resource_dir: []const u8, env: *EnvMap, -) !?[]const u8 { - // Accumulates the arguments that will form the final shell command line. - // We can build this list on the stack because we're just temporarily - // referencing other slices, but we can fall back to heap in extreme cases. - var args_alloc = std.heap.stackFallback(1024, alloc); - var args = try std.ArrayList([]const u8).initCapacity(args_alloc.get(), 2); +) !?config.Command { + var args = try std.ArrayList([:0]const u8).initCapacity(alloc, 2); defer args.deinit(); // Iterator that yields each argument in the original command line. // This will allocate once proportionate to the command line length. - var iter = try std.process.ArgIteratorGeneral(.{}).init(alloc, command); + var iter = try command.argIterator(alloc); defer iter.deinit(); // Start accumulating arguments with the executable and `--posix` mode flag. if (iter.next()) |exe| { - try args.append(exe); + try args.append(try alloc.dupeZ(u8, exe)); } else return null; try args.append("--posix"); @@ -267,17 +283,17 @@ fn setupBash( if (std.mem.indexOfScalar(u8, arg, 'c') != null) { return null; } - try args.append(arg); + try args.append(try alloc.dupeZ(u8, arg)); } else if (std.mem.eql(u8, arg, "-") or std.mem.eql(u8, arg, "--")) { // All remaining arguments should be passed directly to the shell // command. We shouldn't perform any further option processing. - try args.append(arg); + try args.append(try alloc.dupeZ(u8, arg)); while (iter.next()) |remaining_arg| { - try args.append(remaining_arg); + try args.append(try alloc.dupeZ(u8, remaining_arg)); } break; } else { - try args.append(arg); + try args.append(try alloc.dupeZ(u8, arg)); } } try env.put("GHOSTTY_BASH_INJECT", inject.slice()); @@ -310,30 +326,36 @@ fn setupBash( ); try env.put("ENV", integ_dir); - // Join the accumulated arguments to form the final command string. - return try std.mem.join(alloc, " ", args.items); + // Since we built up a command line, we don't need to wrap it in + // ANOTHER shell anymore and can do a direct command. + return .{ .direct = try args.toOwnedSlice() }; } test "bash" { const testing = std.testing; - const alloc = testing.allocator; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); var env = EnvMap.init(alloc); defer env.deinit(); - const command = try setupBash(alloc, "bash", ".", &env); - defer if (command) |c| alloc.free(c); + const command = try setupBash(alloc, .{ .shell = "bash" }, ".", &env); - try testing.expectEqualStrings("bash --posix", command.?); + try testing.expectEqual(2, command.?.direct.len); + try testing.expectEqualStrings("bash", command.?.direct[0]); + try testing.expectEqualStrings("--posix", command.?.direct[1]); try testing.expectEqualStrings("./shell-integration/bash/ghostty.bash", env.get("ENV").?); try testing.expectEqualStrings("1", env.get("GHOSTTY_BASH_INJECT").?); } test "bash: unsupported options" { const testing = std.testing; - const alloc = testing.allocator; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); - const cmdlines = [_][]const u8{ + const cmdlines = [_][:0]const u8{ "bash --posix", "bash --rcfile script.sh --posix", "bash --init-file script.sh --posix", @@ -345,7 +367,7 @@ test "bash: unsupported options" { var env = EnvMap.init(alloc); defer env.deinit(); - try testing.expect(try setupBash(alloc, cmdline, ".", &env) == null); + try testing.expect(try setupBash(alloc, .{ .shell = cmdline }, ".", &env) == null); try testing.expect(env.get("GHOSTTY_BASH_INJECT") == null); try testing.expect(env.get("GHOSTTY_BASH_RCFILE") == null); try testing.expect(env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE") == null); @@ -354,17 +376,20 @@ test "bash: unsupported options" { test "bash: inject flags" { const testing = std.testing; - const alloc = testing.allocator; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); // bash --norc { var env = EnvMap.init(alloc); defer env.deinit(); - const command = try setupBash(alloc, "bash --norc", ".", &env); - defer if (command) |c| alloc.free(c); + const command = try setupBash(alloc, .{ .shell = "bash --norc" }, ".", &env); - try testing.expectEqualStrings("bash --posix", command.?); + try testing.expectEqual(2, command.?.direct.len); + try testing.expectEqualStrings("bash", command.?.direct[0]); + try testing.expectEqualStrings("--posix", command.?.direct[1]); try testing.expectEqualStrings("1 --norc", env.get("GHOSTTY_BASH_INJECT").?); } @@ -373,52 +398,55 @@ test "bash: inject flags" { var env = EnvMap.init(alloc); defer env.deinit(); - const command = try setupBash(alloc, "bash --noprofile", ".", &env); - defer if (command) |c| alloc.free(c); + const command = try setupBash(alloc, .{ .shell = "bash --noprofile" }, ".", &env); - try testing.expectEqualStrings("bash --posix", command.?); + try testing.expectEqual(2, command.?.direct.len); + try testing.expectEqualStrings("bash", command.?.direct[0]); + try testing.expectEqualStrings("--posix", command.?.direct[1]); try testing.expectEqualStrings("1 --noprofile", env.get("GHOSTTY_BASH_INJECT").?); } } test "bash: rcfile" { const testing = std.testing; - const alloc = testing.allocator; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); var env = EnvMap.init(alloc); defer env.deinit(); // bash --rcfile { - const command = try setupBash(alloc, "bash --rcfile profile.sh", ".", &env); - defer if (command) |c| alloc.free(c); - - try testing.expectEqualStrings("bash --posix", command.?); + const command = try setupBash(alloc, .{ .shell = "bash --rcfile profile.sh" }, ".", &env); + try testing.expectEqual(2, command.?.direct.len); + try testing.expectEqualStrings("bash", command.?.direct[0]); + try testing.expectEqualStrings("--posix", command.?.direct[1]); try testing.expectEqualStrings("profile.sh", env.get("GHOSTTY_BASH_RCFILE").?); } // bash --init-file { - const command = try setupBash(alloc, "bash --init-file profile.sh", ".", &env); - defer if (command) |c| alloc.free(c); - - try testing.expectEqualStrings("bash --posix", command.?); + const command = try setupBash(alloc, .{ .shell = "bash --init-file profile.sh" }, ".", &env); + try testing.expectEqual(2, command.?.direct.len); + try testing.expectEqualStrings("bash", command.?.direct[0]); + try testing.expectEqualStrings("--posix", command.?.direct[1]); try testing.expectEqualStrings("profile.sh", env.get("GHOSTTY_BASH_RCFILE").?); } } test "bash: HISTFILE" { const testing = std.testing; - const alloc = testing.allocator; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); // HISTFILE unset { var env = EnvMap.init(alloc); defer env.deinit(); - const command = try setupBash(alloc, "bash", ".", &env); - defer if (command) |c| alloc.free(c); - + _ = try setupBash(alloc, .{ .shell = "bash" }, ".", &env); try testing.expect(std.mem.endsWith(u8, env.get("HISTFILE").?, ".bash_history")); try testing.expectEqualStrings("1", env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE").?); } @@ -430,9 +458,7 @@ test "bash: HISTFILE" { try env.put("HISTFILE", "my_history"); - const command = try setupBash(alloc, "bash", ".", &env); - defer if (command) |c| alloc.free(c); - + _ = try setupBash(alloc, .{ .shell = "bash" }, ".", &env); try testing.expectEqualStrings("my_history", env.get("HISTFILE").?); try testing.expect(env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE") == null); } @@ -440,25 +466,35 @@ test "bash: HISTFILE" { test "bash: additional arguments" { const testing = std.testing; - const alloc = testing.allocator; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); var env = EnvMap.init(alloc); defer env.deinit(); // "-" argument separator { - const command = try setupBash(alloc, "bash - --arg file1 file2", ".", &env); - defer if (command) |c| alloc.free(c); - - try testing.expectEqualStrings("bash --posix - --arg file1 file2", command.?); + const command = try setupBash(alloc, .{ .shell = "bash - --arg file1 file2" }, ".", &env); + try testing.expectEqual(6, command.?.direct.len); + try testing.expectEqualStrings("bash", command.?.direct[0]); + try testing.expectEqualStrings("--posix", command.?.direct[1]); + try testing.expectEqualStrings("-", command.?.direct[2]); + try testing.expectEqualStrings("--arg", command.?.direct[3]); + try testing.expectEqualStrings("file1", command.?.direct[4]); + try testing.expectEqualStrings("file2", command.?.direct[5]); } // "--" argument separator { - const command = try setupBash(alloc, "bash -- --arg file1 file2", ".", &env); - defer if (command) |c| alloc.free(c); - - try testing.expectEqualStrings("bash --posix -- --arg file1 file2", command.?); + const command = try setupBash(alloc, .{ .shell = "bash -- --arg file1 file2" }, ".", &env); + try testing.expectEqual(6, command.?.direct.len); + try testing.expectEqualStrings("bash", command.?.direct[0]); + try testing.expectEqualStrings("--posix", command.?.direct[1]); + try testing.expectEqualStrings("--", command.?.direct[2]); + try testing.expectEqualStrings("--arg", command.?.direct[3]); + try testing.expectEqualStrings("file1", command.?.direct[4]); + try testing.expectEqualStrings("file2", command.?.direct[5]); } } From f0ade53fd23bb029a52093aa2dfb16739fb30f45 Mon Sep 17 00:00:00 2001 From: trag1c Date: Fri, 11 Apr 2025 00:48:24 +0200 Subject: [PATCH 32/73] ci: add a script and workflow for requesting i18n review --- .github/scripts/request_review.py | 115 ++++++++++++++++++++++++++++++ .github/workflows/review.yml | 37 ++++++++++ flake.nix | 1 + nix/devShell.nix | 4 ++ 4 files changed, 157 insertions(+) create mode 100644 .github/scripts/request_review.py create mode 100644 .github/workflows/review.yml diff --git a/.github/scripts/request_review.py b/.github/scripts/request_review.py new file mode 100644 index 000000000..1a53e82e4 --- /dev/null +++ b/.github/scripts/request_review.py @@ -0,0 +1,115 @@ +# /// script +# requires-python = ">=3.9" +# dependencies = [ +# "githubkit", +# ] +# /// + +import asyncio +import os +import re +from itertools import chain + +from githubkit import GitHub + +ORG_NAME = "ghostty-org" +REPO_NAME = "ghostty" +ALLOWED_PARENT_TEAM = "localization" +LOCALIZATION_TEAM_NAME_PATTERN = re.compile(r"[a-z]{2}_[A-Z]{2}") + +gh = GitHub(os.environ["GITHUB_TOKEN"]) + + +async def fetch_and_parse_codeowners() -> dict[str, str]: + content = ( + await gh.rest.repos.async_get_content( + ORG_NAME, + REPO_NAME, + "CODEOWNERS", + headers={"Accept": "application/vnd.github.raw+json"}, + ) + ).text + + codeowners: dict[str, str] = {} + for line in content.splitlines(): + if not line or line.lstrip().startswith("#"): + continue + # This assumes that all entries only list one owner + # and that this owner is a team (ghostty-org/foobar) + path, owner = line.split() + codeowners[path.lstrip("/")] = owner.removeprefix(f"@{ORG_NAME}/") + return codeowners + + +async def get_team_members(team_name: str) -> list[str]: + team = (await gh.rest.teams.async_get_by_name(ORG_NAME, team_name)).parsed_data + if team.parent and team.parent.slug == ALLOWED_PARENT_TEAM: + members = ( + await gh.rest.teams.async_list_members_in_org(ORG_NAME, team_name) + ).parsed_data + return [m.login for m in members] + return [] + + +async def get_changed_files(pr_number: int) -> list[str]: + diff_entries = ( + await gh.rest.pulls.async_list_files( + ORG_NAME, + REPO_NAME, + pr_number, + per_page=3000, + headers={"Accept": "application/vnd.github+json"}, + ) + ).parsed_data + return [d.filename for d in diff_entries] + + +async def request_review(pr_number: int, pr_author: str, *users: str) -> None: + await asyncio.gather( + *( + gh.rest.pulls.async_request_reviewers( + ORG_NAME, + REPO_NAME, + pr_number, + headers={"Accept": "application/vnd.github+json"}, + data={"reviewers": [user]}, + ) + for user in users + if user != pr_author + ) + ) + + +def is_localization_team(team_name: str) -> bool: + return LOCALIZATION_TEAM_NAME_PATTERN.fullmatch(team_name) is not None + + +async def main() -> None: + pr_number = int(os.environ["PR_NUMBER"]) + changed_files = await get_changed_files(pr_number) + pr_author = ( + await gh.rest.pulls.async_get(ORG_NAME, REPO_NAME, pr_number) + ).parsed_data.user.login + localization_codewners = { + path: owner + for path, owner in (await fetch_and_parse_codeowners()).items() + if is_localization_team(owner) + } + + found_owners = set[str]() + for file in changed_files: + for path, owner in localization_codewners.items(): + if file.startswith(path): + break + else: + continue + found_owners.add(owner) + + member_lists = await asyncio.gather( + *(get_team_members(owner) for owner in found_owners) + ) + await request_review(pr_number, pr_author, *chain.from_iterable(member_lists)) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml new file mode 100644 index 000000000..9abe0b5e2 --- /dev/null +++ b/.github/workflows/review.yml @@ -0,0 +1,37 @@ +name: Request Review + +on: + pull_request: + types: + - opened + - synchronize + +env: + PY_COLORS: 1 + +jobs: + review: + runs-on: namespace-profile-ghostty-xsm + steps: + - uses: actions/checkout@v4 + + - name: Setup Cache + uses: namespacelabs/nscloud-cache-action@v1.2.0 + with: + path: | + /nix + /zig + + - uses: cachix/install-nix-action@v30 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@v15 + with: + name: ghostty + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + + - name: Request Localization Review + env: + GITHUB_TOKEN: ${{ secrets.GH_REVIEW_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: nix develop -c uv run .github/scripts/request_review.py diff --git a/flake.nix b/flake.nix index c8e53d7e9..d4c6aa6ca 100644 --- a/flake.nix +++ b/flake.nix @@ -51,6 +51,7 @@ devShell.${system} = pkgs-stable.callPackage ./nix/devShell.nix { zig = zig.packages.${system}."0.14.0"; wraptest = pkgs-stable.callPackage ./nix/wraptest.nix {}; + uv = pkgs-unstable.uv; # remove once blueprint-compiler 0.16.0 is in the stable nixpkgs blueprint-compiler = pkgs-unstable.blueprint-compiler; zon2nix = zon2nix; diff --git a/nix/devShell.nix b/nix/devShell.nix index 6949744d0..5b69f882b 100644 --- a/nix/devShell.nix +++ b/nix/devShell.nix @@ -57,6 +57,7 @@ pandoc, hyperfine, typos, + uv, wayland, wayland-scanner, wayland-protocols, @@ -109,6 +110,9 @@ in # Localization gettext + # CI + uv + # We need these GTK-related deps on all platform so we can build # dist tarballs. blueprint-compiler From 02bb81ad44155581cb492748b7fb323fd419d6ca Mon Sep 17 00:00:00 2001 From: halosatrio <63773815+halosatrio@users.noreply.github.com> Date: Thu, 20 Mar 2025 15:33:28 +0700 Subject: [PATCH 33/73] i18n: add indonesian translation --- po/id_ID.UTF-8.po | 266 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 po/id_ID.UTF-8.po diff --git a/po/id_ID.UTF-8.po b/po/id_ID.UTF-8.po new file mode 100644 index 000000000..d5acf3b21 --- /dev/null +++ b/po/id_ID.UTF-8.po @@ -0,0 +1,266 @@ +# Indonesian translations for com.mitchellh.ghostty package. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# Satrio Bayu Aji , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-03-19 08:28-0700\n" +"PO-Revision-Date: 2025-03-20 15:19+0700\n" +"Last-Translator: Satrio Bayu Aji \n" +"Language-Team: Indonesian \n" +"Language: id\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "Ubah Judul Terminal" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "Biarkan kosong untuk mengembalikan judul bawaan." + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "Batal" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "OK" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "Kesalahan Konfigurasi" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "" +"Ditemukan satu atau lebih kesalahan konfigurasi. Silakan tinjau kesalahan di bawah ini, " +"dan muat ulang konfigurasi anda atau abaikan kesalahan ini." + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "Abaikan" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Reload Configuration" +msgstr "Muat ulang Konfigurasi" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "Salin" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 +msgid "Paste" +msgstr "Tempel" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "Hapus" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "Atur ulang" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "Bagi panel" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "Ubah Judul…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "Bagi panel ke Atas" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "Bagi panel ke Bawah" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "Bagi panel ke Kiri" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "Bagi panel ke Kanan" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "Tab" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:246 +msgid "New Tab" +msgstr "Tab Baru" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "Tutup Tab" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "Jendela" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "Jendela Baru" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "Tutup Jendela" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "Konfigurasi" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Open Configuration" +msgstr "Buka Konfigurasi" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Terminal Inspector" +msgstr "Inspektur Terminal" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 +#: src/apprt/gtk/Window.zig:960 +msgid "About Ghostty" +msgstr "Tentang Ghostty" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +msgid "Quit" +msgstr "Keluar" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "Mengesahkan Akses Papan klip" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Aplikasi sedang mencoba membaca dari papan klip. Isi papan klip " +"saat ini ditampilkan di bawah ini." + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "Menyangkal" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "Izinkan" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Aplikasi sedang mencoba menulis ke papan klip. Isi papan klip " +"saat ini ditampilkan di bawah ini." + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "Peringatan: Tempelan yang Berpotensi Tidak aman" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "" +"Menempelkan teks ini ke terminal mungkin berbahaya karena sepertinya +"beberapa perintah mungkin dijalankan." + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Inspektur Terminal" + +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Disalin ke papan klip" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "Tutup" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "Keluar dari Ghostty?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "Tutup Jendela?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "Tutup Tab?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "Tutup Layar?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "Semua sesi terminal akan diakhiri." + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "Semua sesi terminal di jendela ini akan diakhiri." + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "Semua sesi terminal di tab ini akan diakhiri." + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "Proses yang sedang berjalan dalam layar ini akan dihentikan." + +#: src/apprt/gtk/Window.zig:200 +msgid "Main Menu" +msgstr "Menu Utama" + +#: src/apprt/gtk/Window.zig:221 +msgid "View Open Tabs" +msgstr "Lihat Tabs Terbuka" + +#: src/apprt/gtk/Window.zig:295 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "⚠️ Anda sedang menjalankan versi debug dari Ghostty! Performa akan menurun." + +#: src/apprt/gtk/Window.zig:725 +msgid "Reloaded the configuration" +msgstr "Memuat ulang konfigurasi" + +#: src/apprt/gtk/Window.zig:941 +msgid "Ghostty Developers" +msgstr "Pengembang Ghostty" From d903cc9827d29cd2edc156cdff338ec2fb3a93e7 Mon Sep 17 00:00:00 2001 From: halosatrio <63773815+halosatrio@users.noreply.github.com> Date: Thu, 20 Mar 2025 15:39:56 +0700 Subject: [PATCH 34/73] i18n: fix translation --- po/id_ID.UTF-8.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/po/id_ID.UTF-8.po b/po/id_ID.UTF-8.po index d5acf3b21..717671a64 100644 --- a/po/id_ID.UTF-8.po +++ b/po/id_ID.UTF-8.po @@ -197,7 +197,7 @@ msgid "" "Pasting this text into the terminal may be dangerous as it looks like some " "commands may be executed." msgstr "" -"Menempelkan teks ini ke terminal mungkin berbahaya karena sepertinya +"Menempelkan teks ini ke terminal mungkin berbahaya karena sepertinya " "beberapa perintah mungkin dijalankan." #: src/apprt/gtk/inspector.zig:144 @@ -226,7 +226,7 @@ msgstr "Tutup Tab?" #: src/apprt/gtk/CloseDialog.zig:90 msgid "Close Split?" -msgstr "Tutup Layar?" +msgstr "Tutup Bagi panel?" #: src/apprt/gtk/CloseDialog.zig:96 msgid "All terminal sessions will be terminated." From 1222e80eb14ea4f14cee18174499a02fecdb0e57 Mon Sep 17 00:00:00 2001 From: halosatrio <63773815+halosatrio@users.noreply.github.com> Date: Thu, 20 Mar 2025 15:48:03 +0700 Subject: [PATCH 35/73] i18n: add id_ID code locale --- src/os/i18n.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/os/i18n.zig b/src/os/i18n.zig index baae73e46..80c63352f 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -29,6 +29,7 @@ pub const locales = [_][:0]const u8{ "nb_NO.UTF-8", "uk_UA.UTF-8", "pl_PL.UTF-8", + "id_ID.UTF-8", }; /// Set for faster membership lookup of locales. From 561a0e3897a53b07f326e364c244d43c5c3c8205 Mon Sep 17 00:00:00 2001 From: halosatrio <63773815+halosatrio@users.noreply.github.com> Date: Thu, 20 Mar 2025 18:41:04 +0700 Subject: [PATCH 36/73] i18n: fix capitalization and some translation --- po/id_ID.UTF-8.po | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/po/id_ID.UTF-8.po b/po/id_ID.UTF-8.po index 717671a64..c8b89a89e 100644 --- a/po/id_ID.UTF-8.po +++ b/po/id_ID.UTF-8.po @@ -18,7 +18,7 @@ msgstr "" #: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 msgid "Change Terminal Title" -msgstr "Ubah Judul Terminal" +msgstr "Ubah judul terminal" #: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 msgid "Leave blank to restore the default title." @@ -35,7 +35,7 @@ msgstr "OK" #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 msgid "Configuration Errors" -msgstr "Kesalahan Konfigurasi" +msgstr "Kesalahan konfigurasi" #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 msgid "" @@ -53,7 +53,7 @@ msgstr "Abaikan" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 msgid "Reload Configuration" -msgstr "Muat ulang Konfigurasi" +msgstr "Muat ulang konfigurasi" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 @@ -79,32 +79,32 @@ msgstr "Atur ulang" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 msgid "Split" -msgstr "Bagi panel" +msgstr "Belah" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 msgid "Change Title…" -msgstr "Ubah Judul…" +msgstr "Ubah judul…" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 msgid "Split Up" -msgstr "Bagi panel ke Atas" +msgstr "Belah atas" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 msgid "Split Down" -msgstr "Bagi panel ke Bawah" +msgstr "Belah bawah" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 msgid "Split Left" -msgstr "Bagi panel ke Kiri" +msgstr "Belah kiri" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 msgid "Split Right" -msgstr "Bagi panel ke Kanan" +msgstr "Belah kanan" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" @@ -114,12 +114,12 @@ msgstr "Tab" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 #: src/apprt/gtk/Window.zig:246 msgid "New Tab" -msgstr "Tab Baru" +msgstr "Tab baru" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 msgid "Close Tab" -msgstr "Tutup Tab" +msgstr "Tutup tab" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 msgid "Window" @@ -128,12 +128,12 @@ msgstr "Jendela" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 msgid "New Window" -msgstr "Jendela Baru" +msgstr "Jendela baru" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 msgid "Close Window" -msgstr "Tutup Jendela" +msgstr "Tutup jendela" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 msgid "Config" @@ -142,11 +142,11 @@ msgstr "Konfigurasi" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 msgid "Open Configuration" -msgstr "Buka Konfigurasi" +msgstr "Buka konfigurasi" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 msgid "Terminal Inspector" -msgstr "Inspektur Terminal" +msgstr "Inspektur terminal" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 #: src/apprt/gtk/Window.zig:960 @@ -160,7 +160,7 @@ msgstr "Keluar" #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 msgid "Authorize Clipboard Access" -msgstr "Mengesahkan Akses Papan klip" +msgstr "Mengesahkan akses papan klip" #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 msgid "" @@ -190,7 +190,7 @@ msgstr "" #: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 msgid "Warning: Potentially Unsafe Paste" -msgstr "Peringatan: Tempelan yang Berpotensi Tidak aman" +msgstr "Peringatan: Tempelan yang berpotensi tidak aman" #: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 msgid "" @@ -202,7 +202,7 @@ msgstr "" #: src/apprt/gtk/inspector.zig:144 msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty: Inspektur Terminal" +msgstr "Ghostty: Inspektur terminal" #: src/apprt/gtk/Surface.zig:1243 msgid "Copied to clipboard" @@ -218,15 +218,15 @@ msgstr "Keluar dari Ghostty?" #: src/apprt/gtk/CloseDialog.zig:88 msgid "Close Window?" -msgstr "Tutup Jendela?" +msgstr "Tutup jendela?" #: src/apprt/gtk/CloseDialog.zig:89 msgid "Close Tab?" -msgstr "Tutup Tab?" +msgstr "Tutup tab?" #: src/apprt/gtk/CloseDialog.zig:90 msgid "Close Split?" -msgstr "Tutup Bagi panel?" +msgstr "Tutup belahan?" #: src/apprt/gtk/CloseDialog.zig:96 msgid "All terminal sessions will be terminated." @@ -242,15 +242,15 @@ msgstr "Semua sesi terminal di tab ini akan diakhiri." #: src/apprt/gtk/CloseDialog.zig:99 msgid "The currently running process in this split will be terminated." -msgstr "Proses yang sedang berjalan dalam layar ini akan dihentikan." +msgstr "Proses yang sedang berjalan dalam belahan ini akan diakhiri." #: src/apprt/gtk/Window.zig:200 msgid "Main Menu" -msgstr "Menu Utama" +msgstr "Menu utama" #: src/apprt/gtk/Window.zig:221 msgid "View Open Tabs" -msgstr "Lihat Tabs Terbuka" +msgstr "Lihat tab terbuka" #: src/apprt/gtk/Window.zig:295 msgid "" From 7adc2954c3b2a29892aa460cc13bd296ce1d5081 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 11 Apr 2025 09:26:59 -0700 Subject: [PATCH 37/73] update CODEOWNERS --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index b76c7b3da..59998001c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -159,6 +159,7 @@ /po/README_TRANSLATORS.md @ghostty-org/localization /po/com.mitchellh.ghostty.pot @ghostty-org/localization /po/de_DE.UTF-8.po @ghostty-org/de_DE +/po/id_ID.UTF-8.po @ghostty-org/id_ID /po/nb_NO.UTF-8.po @ghostty-org/nb_NO /po/pl_PL.UTF-8.po @ghostty-org/pl_PL /po/uk_UA.UTF-8.po @ghostty-org/uk_UA From 20ef2150de979d27e1f5e0117d40adb570dfc21c Mon Sep 17 00:00:00 2001 From: Lon Sagisawa Date: Sat, 22 Mar 2025 15:21:10 +0900 Subject: [PATCH 38/73] i18n: Add Japanese translations --- CODEOWNERS | 1 + po/ja_JP.UTF-8.po | 268 ++++++++++++++++++++++++++++++++++++++++++++++ src/os/i18n.zig | 1 + 3 files changed, 270 insertions(+) create mode 100644 po/ja_JP.UTF-8.po diff --git a/CODEOWNERS b/CODEOWNERS index 59998001c..a7571ae43 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -160,6 +160,7 @@ /po/com.mitchellh.ghostty.pot @ghostty-org/localization /po/de_DE.UTF-8.po @ghostty-org/de_DE /po/id_ID.UTF-8.po @ghostty-org/id_ID +/po/ja_JP.UTF-8.po @ghostty-org/ja_JP /po/nb_NO.UTF-8.po @ghostty-org/nb_NO /po/pl_PL.UTF-8.po @ghostty-org/pl_PL /po/uk_UA.UTF-8.po @ghostty-org/uk_UA diff --git a/po/ja_JP.UTF-8.po b/po/ja_JP.UTF-8.po new file mode 100644 index 000000000..8cdb0b38c --- /dev/null +++ b/po/ja_JP.UTF-8.po @@ -0,0 +1,268 @@ +# Japanese translations for com.mitchellh.ghostty package +# com.mitchellh.ghostty パッケージに対する英訳. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# Lon Sagisawa , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-03-19 08:28-0700\n" +"PO-Revision-Date: 2025-03-21 00:08+0900\n" +"Last-Translator: Lon Sagisawa \n" +"Language-Team: Japanese\n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "ターミナルのタイトルを変更する" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "空白にした場合、デフォルトのタイトルを使用します。" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "キャンセル" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "OK" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "設定エラー" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "" +"設定ファイルにエラーがあります。以下のエラーを確認し、" +"設定ファイルの再読み込みをするか、無視してください。" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "無視" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Reload Configuration" +msgstr "設定ファイルの再読み込み" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "コピー" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 +msgid "Paste" +msgstr "貼り付け" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "クリア" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "リセット" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "分割" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "タイトルを変更…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "上に分割" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "下に分割" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "左に分割" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "右に分割" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "タブ" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:246 +msgid "New Tab" +msgstr "新しいタブ" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "タブを閉じる" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "ウィンドウ" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "新しいウィンドウ" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "ウィンドウを閉じる" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "設定" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Open Configuration" +msgstr "設定ファイルを開く" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Terminal Inspector" +msgstr "端末インスペクター" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 +#: src/apprt/gtk/Window.zig:960 +msgid "About Ghostty" +msgstr "Ghosttyについて" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +msgid "Quit" +msgstr "終了" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "クリップボードへのアクセスを承認" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"アプリケーションがクリップボードを読み取ろうとしています。" +"現在のクリップボードの内容は以下の通りです。" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "拒否" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "許可" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"アプリケーションがクリップボードに書き込もうとしています。" +"現在のクリップボードの内容は以下の通りです。" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "警告: 危険な可能性のあるペースト" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "" +"このテキストには実行可能なコマンドが含まれており、" +"ターミナルに貼り付けるのは危険な可能性があります。" + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: 端末インスペクター" + +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "クリップボードにコピーしました" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "閉じる" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "Ghosttyを終了しますか?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "ウィンドウを閉じますか?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "タブを閉じますか?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "分割ウィンドウを閉じますか?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "すべてのターミナルセッションが終了されます。" + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "ウィンドウ内のすべてのターミナルセッションが終了されます。" + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "タブ内のすべてのターミナルセッションが終了されます。" + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "分割ウィンドウ内のすべてのターミナルセッションが終了されます。" + +#: src/apprt/gtk/Window.zig:200 +msgid "Main Menu" +msgstr "メインメニュー" + +#: src/apprt/gtk/Window.zig:221 +msgid "View Open Tabs" +msgstr "開いているすべてのタブを表示" + +#: src/apprt/gtk/Window.zig:295 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "⚠️ Ghosttyのデバッグビルドを実行しています! パフォーマンスが低下しています。" + +#: src/apprt/gtk/Window.zig:725 +msgid "Reloaded the configuration" +msgstr "設定を再読み込みしました" + +#: src/apprt/gtk/Window.zig:941 +msgid "Ghostty Developers" +msgstr "Ghostty 開発者" diff --git a/src/os/i18n.zig b/src/os/i18n.zig index 80c63352f..9a56eade3 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -29,6 +29,7 @@ pub const locales = [_][:0]const u8{ "nb_NO.UTF-8", "uk_UA.UTF-8", "pl_PL.UTF-8", + "ja_JP.UTF-8", "id_ID.UTF-8", }; From 10a90b5b67f1d3d9d28ce14d006f27b7ec603692 Mon Sep 17 00:00:00 2001 From: Lon Sagisawa Date: Sat, 22 Mar 2025 17:57:02 +0900 Subject: [PATCH 39/73] fix: inconsistency around space --- po/ja_JP.UTF-8.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/po/ja_JP.UTF-8.po b/po/ja_JP.UTF-8.po index 8cdb0b38c..7a4ee6929 100644 --- a/po/ja_JP.UTF-8.po +++ b/po/ja_JP.UTF-8.po @@ -153,7 +153,7 @@ msgstr "端末インスペクター" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 #: src/apprt/gtk/Window.zig:960 msgid "About Ghostty" -msgstr "Ghosttyについて" +msgstr "Ghostty について" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 msgid "Quit" @@ -216,7 +216,7 @@ msgstr "閉じる" #: src/apprt/gtk/CloseDialog.zig:87 msgid "Quit Ghostty?" -msgstr "Ghosttyを終了しますか?" +msgstr "Ghostty を終了しますか?" #: src/apprt/gtk/CloseDialog.zig:88 msgid "Close Window?" @@ -257,7 +257,7 @@ msgstr "開いているすべてのタブを表示" #: src/apprt/gtk/Window.zig:295 msgid "" "⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "⚠️ Ghosttyのデバッグビルドを実行しています! パフォーマンスが低下しています。" +msgstr "⚠️ Ghostty のデバッグビルドを実行しています! パフォーマンスが低下しています。" #: src/apprt/gtk/Window.zig:725 msgid "Reloaded the configuration" From e2a8a3243c00f248c7b2296ff436cbcea51f7298 Mon Sep 17 00:00:00 2001 From: Kirwiisp <59315476+Kirwiisp@users.noreply.github.com> Date: Sat, 22 Mar 2025 09:23:01 +0100 Subject: [PATCH 40/73] i18n: Add French translation --- CODEOWNERS | 1 + po/fr_FR.UTF-8.po | 267 ++++++++++++++++++++++++++++++++++++++++++++++ src/os/i18n.zig | 3 +- 3 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 po/fr_FR.UTF-8.po diff --git a/CODEOWNERS b/CODEOWNERS index a7571ae43..fac1a44f9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -165,6 +165,7 @@ /po/pl_PL.UTF-8.po @ghostty-org/pl_PL /po/uk_UA.UTF-8.po @ghostty-org/uk_UA /po/zh_CN.UTF-8.po @ghostty-org/zh_CN +/po/fr_FR.UTF-8.po @ghostty-org/fr_FR # Packaging - Snap /snap/ @ghostty-org/snap diff --git a/po/fr_FR.UTF-8.po b/po/fr_FR.UTF-8.po new file mode 100644 index 000000000..3b805133e --- /dev/null +++ b/po/fr_FR.UTF-8.po @@ -0,0 +1,267 @@ +# French translations for com.mitchellh.ghostty package. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# Kirwiisp , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-03-19 08:28-0700\n" +"PO-Revision-Date: 2025-03-22 09:31+0100\n" +"Last-Translator: Kirwiisp \n" +"Language-Team: French \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "Changer nom du terminal" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "Laisser vide pour restaurer le titre par défaut" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "Annuler" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "OK" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "Erreur de configuration" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "Une ou plusieurs erreurs de configuration ont été trouvée(s). Veuillez lire les erreurs ci-dessous, et recharger votre configuration ou bien ignorer ces erreurs." + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "Ignorer" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Reload Configuration" +msgstr "Recharger la configuration" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "Copier" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 +msgid "Paste" +msgstr "Coller" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "Tout effacer" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "Réinitialiser" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "Créer panneau" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "Changer le titre" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "Panneau en haut" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "Panneau en bas" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "Panneau à gauche" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "Panneau à droite" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "Onglet" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:246 +msgid "New Tab" +msgstr "Nouvel onglet" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "Fermer onglet" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "Fenêtre" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "Nouvelle fenêtre" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "Fermer la fenêtre" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "Config" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Open Configuration" +msgstr "Ouvrir la configuration" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Terminal Inspector" +msgstr "Inspecteur de terminal" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 +#: src/apprt/gtk/Window.zig:960 +msgid "About Ghostty" +msgstr "À propos de Ghostty" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +msgid "Quit" +msgstr "Quitter" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "Autoriser l'accès au presse-papier" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Une application essai de lire depuis le presse-papier." +"Le contenu actuel du presse-papier est affiché ci-dessous" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "Refuser" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "Autoriser" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Une application essai d'écrire dans le presse-papier." +"Le contenu actuel du presse-papier est affiché ci-dessous" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "Warning: Collage potentiellement non sécurisé" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "" +"Coller ce texte dans le terminal pourrait être dangereux, " +"il semblerait que certaines commandes pourraient être exécutées" + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Inspecteur" + +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Copié dans le presse-papier" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "Fermer" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "Quitter Ghostty?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "Fermer la fenêtre?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "Fermer l'onglet?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "Fermer le panneau?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "Toutes les sessions vont être arrêtées" + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "Toutes les sessions de cette fenêtre vont être arrêtées" + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "Toutes les sessions de cet onglet vont être arrêtées" + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "Le processus en cours dans ce panneau va être arrêté" + +#: src/apprt/gtk/Window.zig:200 +msgid "Main Menu" +msgstr "Menu principal" + +#: src/apprt/gtk/Window.zig:221 +msgid "View Open Tabs" +msgstr "Voir les onglets ouverts" + +#: src/apprt/gtk/Window.zig:295 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Vous utilisez une version de débogage de Ghostty! Les performances seront dégradées." + +#: src/apprt/gtk/Window.zig:725 +msgid "Reloaded the configuration" +msgstr "Recharger la configuration" + +#: src/apprt/gtk/Window.zig:941 +msgid "Ghostty Developers" +msgstr "Les developpeurs de Ghostty" + diff --git a/src/os/i18n.zig b/src/os/i18n.zig index 9a56eade3..f0d815c92 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -24,8 +24,9 @@ const log = std.log.scoped(.i18n); /// 3. Most preferred locale for a language without a country code. /// pub const locales = [_][:0]const u8{ - "de_DE.UTF-8", "zh_CN.UTF-8", + "de_DE.UTF-8", + "fr_FR.UTF-8", "nb_NO.UTF-8", "uk_UA.UTF-8", "pl_PL.UTF-8", From 1aa16cdf6b0c9d84dbd79142e80ed2aefd170c74 Mon Sep 17 00:00:00 2001 From: Nicolas G <59315476+Kirwiisp@users.noreply.github.com> Date: Sat, 22 Mar 2025 10:30:33 +0100 Subject: [PATCH 41/73] Fix ponctuation and Warning translation Fix: missing ponctuations on various lines. Change "Warning" to "Attention", might change in the futur if it does not perfectly match common practice. Fix: Multiline on config errors dialog --- po/fr_FR.UTF-8.po | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/po/fr_FR.UTF-8.po b/po/fr_FR.UTF-8.po index 3b805133e..2bf391ceb 100644 --- a/po/fr_FR.UTF-8.po +++ b/po/fr_FR.UTF-8.po @@ -23,7 +23,7 @@ msgstr "Changer nom du terminal" #: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 msgid "Leave blank to restore the default title." -msgstr "Laisser vide pour restaurer le titre par défaut" +msgstr "Laisser vide pour restaurer le titre par défaut." #: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 #: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 @@ -42,7 +42,9 @@ msgstr "Erreur de configuration" msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." -msgstr "Une ou plusieurs erreurs de configuration ont été trouvée(s). Veuillez lire les erreurs ci-dessous, et recharger votre configuration ou bien ignorer ces erreurs." +msgstr "" +"Une ou plusieurs erreurs de configuration ont été trouvée(s). Veuillez lire les erreurs ci-dessous," +"et recharger votre configuration ou bien ignorer ces erreurs." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" @@ -83,7 +85,7 @@ msgstr "Créer panneau" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 msgid "Change Title…" -msgstr "Changer le titre" +msgstr "Changer le titre…" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 @@ -167,7 +169,7 @@ msgid "" "clipboard contents are shown below." msgstr "" "Une application essai de lire depuis le presse-papier." -"Le contenu actuel du presse-papier est affiché ci-dessous" +"Le contenu actuel du presse-papier est affiché ci-dessous." #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 @@ -185,11 +187,11 @@ msgid "" "clipboard contents are shown below." msgstr "" "Une application essai d'écrire dans le presse-papier." -"Le contenu actuel du presse-papier est affiché ci-dessous" +"Le contenu actuel du presse-papier est affiché ci-dessous." #: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 msgid "Warning: Potentially Unsafe Paste" -msgstr "Warning: Collage potentiellement non sécurisé" +msgstr "Attention: Collage potentiellement dangereux" #: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 msgid "" @@ -197,7 +199,7 @@ msgid "" "commands may be executed." msgstr "" "Coller ce texte dans le terminal pourrait être dangereux, " -"il semblerait que certaines commandes pourraient être exécutées" +"il semblerait que certaines commandes pourraient être exécutées." #: src/apprt/gtk/inspector.zig:144 msgid "Ghostty: Terminal Inspector" @@ -229,19 +231,19 @@ msgstr "Fermer le panneau?" #: src/apprt/gtk/CloseDialog.zig:96 msgid "All terminal sessions will be terminated." -msgstr "Toutes les sessions vont être arrêtées" +msgstr "Toutes les sessions vont être arrêtées." #: src/apprt/gtk/CloseDialog.zig:97 msgid "All terminal sessions in this window will be terminated." -msgstr "Toutes les sessions de cette fenêtre vont être arrêtées" +msgstr "Toutes les sessions de cette fenêtre vont être arrêtées." #: src/apprt/gtk/CloseDialog.zig:98 msgid "All terminal sessions in this tab will be terminated." -msgstr "Toutes les sessions de cet onglet vont être arrêtées" +msgstr "Toutes les sessions de cet onglet vont être arrêtées." #: src/apprt/gtk/CloseDialog.zig:99 msgid "The currently running process in this split will be terminated." -msgstr "Le processus en cours dans ce panneau va être arrêté" +msgstr "Le processus en cours dans ce panneau va être arrêté." #: src/apprt/gtk/Window.zig:200 msgid "Main Menu" @@ -264,4 +266,3 @@ msgstr "Recharger la configuration" #: src/apprt/gtk/Window.zig:941 msgid "Ghostty Developers" msgstr "Les developpeurs de Ghostty" - From 930079ca01d49cb22545ed723592b9430db4abc5 Mon Sep 17 00:00:00 2001 From: Kirwiisp <59315476+Kirwiisp@users.noreply.github.com> Date: Wed, 26 Mar 2025 09:05:19 +0100 Subject: [PATCH 42/73] fix: spelling mistakes --- po/fr_FR.UTF-8.po | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/po/fr_FR.UTF-8.po b/po/fr_FR.UTF-8.po index 2bf391ceb..2e9609d94 100644 --- a/po/fr_FR.UTF-8.po +++ b/po/fr_FR.UTF-8.po @@ -19,7 +19,7 @@ msgstr "" #: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 msgid "Change Terminal Title" -msgstr "Changer nom du terminal" +msgstr "Changer le nom du terminal" #: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 msgid "Leave blank to restore the default title." @@ -36,14 +36,14 @@ msgstr "OK" #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 msgid "Configuration Errors" -msgstr "Erreur de configuration" +msgstr "Erreurs de configuration" #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." msgstr "" -"Une ou plusieurs erreurs de configuration ont été trouvée(s). Veuillez lire les erreurs ci-dessous," +"Une ou plusieurs erreurs de configuration ont été trouvées. Veuillez lire les erreurs ci-dessous," "et recharger votre configuration ou bien ignorer ces erreurs." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 @@ -161,15 +161,15 @@ msgstr "Quitter" #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 msgid "Authorize Clipboard Access" -msgstr "Autoriser l'accès au presse-papier" +msgstr "Autoriser l'accès au presse-papiers" #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 msgid "" "An application is attempting to read from the clipboard. The current " "clipboard contents are shown below." msgstr "" -"Une application essai de lire depuis le presse-papier." -"Le contenu actuel du presse-papier est affiché ci-dessous." +"Une application essai de lire depuis le presse-papiers." +"Le contenu actuel du presse-papiers est affiché ci-dessous." #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 @@ -186,7 +186,7 @@ msgid "" "An application is attempting to write to the clipboard. The current " "clipboard contents are shown below." msgstr "" -"Une application essai d'écrire dans le presse-papier." +"Une application essaie d'écrire dans le presse-papiers." "Le contenu actuel du presse-papier est affiché ci-dessous." #: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 @@ -207,7 +207,7 @@ msgstr "Ghostty: Inspecteur" #: src/apprt/gtk/Surface.zig:1243 msgid "Copied to clipboard" -msgstr "Copié dans le presse-papier" +msgstr "Copié dans le presse-papiers" #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" @@ -265,4 +265,4 @@ msgstr "Recharger la configuration" #: src/apprt/gtk/Window.zig:941 msgid "Ghostty Developers" -msgstr "Les developpeurs de Ghostty" +msgstr "Les développeurs de Ghostty" From 5ca5afb13db4db8685664504ce6d4106a8f36970 Mon Sep 17 00:00:00 2001 From: Kirwiisp <59315476+Kirwiisp@users.noreply.github.com> Date: Wed, 26 Mar 2025 13:01:20 +0100 Subject: [PATCH 43/73] fix: spelling and typo --- po/fr_FR.UTF-8.po | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/po/fr_FR.UTF-8.po b/po/fr_FR.UTF-8.po index 2e9609d94..fc5bfd054 100644 --- a/po/fr_FR.UTF-8.po +++ b/po/fr_FR.UTF-8.po @@ -168,7 +168,7 @@ msgid "" "An application is attempting to read from the clipboard. The current " "clipboard contents are shown below." msgstr "" -"Une application essai de lire depuis le presse-papiers." +"Une application essaie de lire depuis le presse-papiers." "Le contenu actuel du presse-papiers est affiché ci-dessous." #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 @@ -187,7 +187,7 @@ msgid "" "clipboard contents are shown below." msgstr "" "Une application essaie d'écrire dans le presse-papiers." -"Le contenu actuel du presse-papier est affiché ci-dessous." +"Le contenu actuel du presse-papiers est affiché ci-dessous." #: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 msgid "Warning: Potentially Unsafe Paste" @@ -215,19 +215,19 @@ msgstr "Fermer" #: src/apprt/gtk/CloseDialog.zig:87 msgid "Quit Ghostty?" -msgstr "Quitter Ghostty?" +msgstr "Quitter Ghostty ?" #: src/apprt/gtk/CloseDialog.zig:88 msgid "Close Window?" -msgstr "Fermer la fenêtre?" +msgstr "Fermer la fenêtre ?" #: src/apprt/gtk/CloseDialog.zig:89 msgid "Close Tab?" -msgstr "Fermer l'onglet?" +msgstr "Fermer l'onglet ?" #: src/apprt/gtk/CloseDialog.zig:90 msgid "Close Split?" -msgstr "Fermer le panneau?" +msgstr "Fermer le panneau ?" #: src/apprt/gtk/CloseDialog.zig:96 msgid "All terminal sessions will be terminated." @@ -257,7 +257,7 @@ msgstr "Voir les onglets ouverts" msgid "" "⚠️ You're running a debug build of Ghostty! Performance will be degraded." msgstr "" -"⚠️ Vous utilisez une version de débogage de Ghostty! Les performances seront dégradées." +"⚠️ Vous utilisez une version de débogage de Ghostty ! Les performances seront dégradées." #: src/apprt/gtk/Window.zig:725 msgid "Reloaded the configuration" From 5be4b0de6b7e43b5b9a0f842f3821114d2d11716 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 11 Apr 2025 09:42:45 -0700 Subject: [PATCH 44/73] CODEOWNERS --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index fac1a44f9..1dbe5af17 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -159,13 +159,13 @@ /po/README_TRANSLATORS.md @ghostty-org/localization /po/com.mitchellh.ghostty.pot @ghostty-org/localization /po/de_DE.UTF-8.po @ghostty-org/de_DE +/po/fr_FR.UTF-8.po @ghostty-org/fr_FR /po/id_ID.UTF-8.po @ghostty-org/id_ID /po/ja_JP.UTF-8.po @ghostty-org/ja_JP /po/nb_NO.UTF-8.po @ghostty-org/nb_NO /po/pl_PL.UTF-8.po @ghostty-org/pl_PL /po/uk_UA.UTF-8.po @ghostty-org/uk_UA /po/zh_CN.UTF-8.po @ghostty-org/zh_CN -/po/fr_FR.UTF-8.po @ghostty-org/fr_FR # Packaging - Snap /snap/ @ghostty-org/snap From df5dd1858affe71fdd401fe11c47f2218508bf18 Mon Sep 17 00:00:00 2001 From: blackzeshi Date: Mon, 24 Mar 2025 00:34:58 +0500 Subject: [PATCH 45/73] add russian translation --- po/ru_RU.UTF-8.po | 261 ++++++++++++++++++++++++++++++++++++++++++++++ src/os/i18n.zig | 1 + 2 files changed, 262 insertions(+) create mode 100644 po/ru_RU.UTF-8.po diff --git a/po/ru_RU.UTF-8.po b/po/ru_RU.UTF-8.po new file mode 100644 index 000000000..f867f4f77 --- /dev/null +++ b/po/ru_RU.UTF-8.po @@ -0,0 +1,261 @@ +# Russian translations for com.mitchellh.ghostty package +# Английские переводы для пакета com.mitchellh.ghostty. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# blackzeshi , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-03-19 08:28-0700\n" +"PO-Revision-Date: 2025-03-24 00:01+0500\n" +"Last-Translator: blackzeshi \n" +"Language-Team: Russian \n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "Изменить заголовок терминала" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "Оставьте пустым, чтобы восстановить исходный заголовок." + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "Отмена" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "ОК" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "Ошибки конфигурации" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "Были обнаружены одна или несколько ошибок конфигурации. Пожалуйста, проверьте ошибки ниже, " + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "Игнорировать" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Reload Configuration" +msgstr "Обновить конфигурацию" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "Копировать" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 +msgid "Paste" +msgstr "Вставить" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "Очистить" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "Сброс" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "Сплит" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "Изменить заголовок…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "Сплит наверх" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "Сплит вниз" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "Сплит влево" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "Сплит вправо" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "Вкладка" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:246 +msgid "New Tab" +msgstr "Новая вкладка" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "Закрыть вкладку" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "Окно" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "Новое окно" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "Закрыть окно" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "Конфигурация" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Open Configuration" +msgstr "Открыть конфигурационный файл" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Terminal Inspector" +msgstr "Инспектор терминала" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 +#: src/apprt/gtk/Window.zig:960 +msgid "About Ghostty" +msgstr "О Ghostty" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +msgid "Quit" +msgstr "Выход" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "Разрешить доступ к буферу обмена" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "Приложение пытается прочитать данные из буфера обмена. Эти данные показаны ниже." + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "Отклонить" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "Разрешить" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "Приложение пытается записать данные в буфер обмена. Эти данные показаны ниже." + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "Внимание! Вставляемые данные могут нанести вред вашей системе" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "Вставка этого текста в терминал может быть опасной. Это выглядит как команды, которые могут быть исполнены." + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: инспектор терминала" + +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Скопировано в буфер обмена" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "Закрыть" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "Закрыть Ghostty?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "Закрыть окно?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "Закрыть вкладку?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "Закрыть сплит-режим?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "Все сессии терминала будут остановлены." + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "Все сессии терминала в этом окне будут остановлены." + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "Все сессии терминала в этой вкладке будут остановлены." + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "Запущенный процесс в этой сплит-области будет остановлен." + +#: src/apprt/gtk/Window.zig:200 +msgid "Main Menu" +msgstr "Главное меню" + +#: src/apprt/gtk/Window.zig:221 +msgid "View Open Tabs" +msgstr "Просмотреть открытые вкладки" + +#: src/apprt/gtk/Window.zig:295 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "Вы запустили Ghostty в режиме отладки! Это может влиять на производительность." + +#: src/apprt/gtk/Window.zig:725 +msgid "Reloaded the configuration" +msgstr "Конфигурация была обновлена" + +#: src/apprt/gtk/Window.zig:941 +msgid "Ghostty Developers" +msgstr "Разработчики Ghostty" diff --git a/src/os/i18n.zig b/src/os/i18n.zig index f0d815c92..bb76e2011 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -32,6 +32,7 @@ pub const locales = [_][:0]const u8{ "pl_PL.UTF-8", "ja_JP.UTF-8", "id_ID.UTF-8", + "ru_RU.UTF-8", }; /// Set for faster membership lookup of locales. From 91f9fdb1be8b5a2a40367cec24f9e28ee58b6656 Mon Sep 17 00:00:00 2001 From: blackzeshi Date: Mon, 24 Mar 2025 20:54:30 +0500 Subject: [PATCH 46/73] some fixes to adding russian translation (i.e. emoji) --- po/ru_RU.UTF-8.po | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/po/ru_RU.UTF-8.po b/po/ru_RU.UTF-8.po index f867f4f77..86e6648f6 100644 --- a/po/ru_RU.UTF-8.po +++ b/po/ru_RU.UTF-8.po @@ -44,7 +44,9 @@ msgstr "Ошибки конфигурации" msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." -msgstr "Были обнаружены одна или несколько ошибок конфигурации. Пожалуйста, проверьте ошибки ниже, " +msgstr "" +"Были обнаружены одна или несколько ошибок конфигурации. Пожалуйста, проверьте ошибки ниже, " +"а также перезагрузите конфигурацию или проигнорируйте эти ошибки." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" @@ -167,7 +169,9 @@ msgstr "Разрешить доступ к буферу обмена" msgid "" "An application is attempting to read from the clipboard. The current " "clipboard contents are shown below." -msgstr "Приложение пытается прочитать данные из буфера обмена. Эти данные показаны ниже." +msgstr "" +"Приложение пытается прочитать данные из буфера обмена. Эти данные " +"отображены ниже." #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 @@ -183,7 +187,9 @@ msgstr "Разрешить" msgid "" "An application is attempting to write to the clipboard. The current " "clipboard contents are shown below." -msgstr "Приложение пытается записать данные в буфер обмена. Эти данные показаны ниже." +msgstr "" +"Приложение пытается записать данные в буфер обмена. Эти данные " +"показаны ниже." #: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 msgid "Warning: Potentially Unsafe Paste" @@ -193,7 +199,9 @@ msgstr "Внимание! Вставляемые данные могут нан msgid "" "Pasting this text into the terminal may be dangerous as it looks like some " "commands may be executed." -msgstr "Вставка этого текста в терминал может быть опасной. Это выглядит как команды, которые могут быть исполнены." +msgstr "" +"Вставка этого текста в терминал может быть опасной. Это выглядит " +"как команды, которые могут быть исполнены." #: src/apprt/gtk/inspector.zig:144 msgid "Ghostty: Terminal Inspector" @@ -250,7 +258,8 @@ msgstr "Просмотреть открытые вкладки" #: src/apprt/gtk/Window.zig:295 msgid "" "⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "Вы запустили Ghostty в режиме отладки! Это может влиять на производительность." +msgstr "" +"⚠️ Вы запустили Ghostty в режиме отладки! Это может влиять на производительность." #: src/apprt/gtk/Window.zig:725 msgid "Reloaded the configuration" From 0d23f7af31905ea3eb78373cb2e37296e0cd618a Mon Sep 17 00:00:00 2001 From: blackzeshi <105582686+zeshi09@users.noreply.github.com> Date: Tue, 25 Mar 2025 20:34:12 +0500 Subject: [PATCH 47/73] Update po/ru_RU.UTF-8.po 248 line changed Co-authored-by: TicClick --- po/ru_RU.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/ru_RU.UTF-8.po b/po/ru_RU.UTF-8.po index 86e6648f6..f10db2d3b 100644 --- a/po/ru_RU.UTF-8.po +++ b/po/ru_RU.UTF-8.po @@ -245,7 +245,7 @@ msgstr "Все сессии терминала в этой вкладке буд #: src/apprt/gtk/CloseDialog.zig:99 msgid "The currently running process in this split will be terminated." -msgstr "Запущенный процесс в этой сплит-области будет остановлен." +msgstr "Процесс, работающий в этой сплит-области, будет остановлен." #: src/apprt/gtk/Window.zig:200 msgid "Main Menu" From f4054daf0d7cda0928be9b0df8686e95460352d7 Mon Sep 17 00:00:00 2001 From: blackzeshi <105582686+zeshi09@users.noreply.github.com> Date: Tue, 25 Mar 2025 20:35:19 +0500 Subject: [PATCH 48/73] Update po/ru_RU.UTF-8.po 262 line about debug build fixed Co-authored-by: TicClick --- po/ru_RU.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/ru_RU.UTF-8.po b/po/ru_RU.UTF-8.po index f10db2d3b..1cce96232 100644 --- a/po/ru_RU.UTF-8.po +++ b/po/ru_RU.UTF-8.po @@ -259,7 +259,7 @@ msgstr "Просмотреть открытые вкладки" msgid "" "⚠️ You're running a debug build of Ghostty! Performance will be degraded." msgstr "" -"⚠️ Вы запустили Ghostty в режиме отладки! Это может влиять на производительность." +"⚠️ Вы запустили отладочную сборку Ghostty! Это может влиять на производительность." #: src/apprt/gtk/Window.zig:725 msgid "Reloaded the configuration" From d0403021a4332e251672c54bef9f6c09dd12adac Mon Sep 17 00:00:00 2001 From: blackzeshi <105582686+zeshi09@users.noreply.github.com> Date: Tue, 25 Mar 2025 20:35:58 +0500 Subject: [PATCH 49/73] Update po/ru_RU.UTF-8.po 2 line fixed Co-authored-by: TicClick --- po/ru_RU.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/ru_RU.UTF-8.po b/po/ru_RU.UTF-8.po index 1cce96232..0811d2338 100644 --- a/po/ru_RU.UTF-8.po +++ b/po/ru_RU.UTF-8.po @@ -1,5 +1,5 @@ # Russian translations for com.mitchellh.ghostty package -# Английские переводы для пакета com.mitchellh.ghostty. +# Русские переводы для пакета com.mitchellh.ghostty. # Copyright (C) 2025 Mitchell Hashimoto # This file is distributed under the same license as the com.mitchellh.ghostty package. # blackzeshi , 2025. From 0d226d139baf29c792866ed47265c826cf072620 Mon Sep 17 00:00:00 2001 From: blackzeshi <105582686+zeshi09@users.noreply.github.com> Date: Sun, 30 Mar 2025 15:18:02 +0500 Subject: [PATCH 50/73] Update po/ru_RU.UTF-8.po Co-authored-by: TicClick --- po/ru_RU.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/ru_RU.UTF-8.po b/po/ru_RU.UTF-8.po index 0811d2338..50e67fa48 100644 --- a/po/ru_RU.UTF-8.po +++ b/po/ru_RU.UTF-8.po @@ -92,7 +92,7 @@ msgstr "Изменить заголовок…" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 msgid "Split Up" -msgstr "Сплит наверх" +msgstr "Сплит вверх" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 From 077ae6a09807322f911314900ff8e4619c208d82 Mon Sep 17 00:00:00 2001 From: blackzeshi <105582686+zeshi09@users.noreply.github.com> Date: Sun, 30 Mar 2025 15:18:24 +0500 Subject: [PATCH 51/73] Update po/ru_RU.UTF-8.po Co-authored-by: TicClick --- po/ru_RU.UTF-8.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/po/ru_RU.UTF-8.po b/po/ru_RU.UTF-8.po index 50e67fa48..a3c21a246 100644 --- a/po/ru_RU.UTF-8.po +++ b/po/ru_RU.UTF-8.po @@ -45,8 +45,8 @@ msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." msgstr "" -"Были обнаружены одна или несколько ошибок конфигурации. Пожалуйста, проверьте ошибки ниже, " -"а также перезагрузите конфигурацию или проигнорируйте эти ошибки." +"Конфигурация содержит ошибки. Проверьте их ниже, а затем" +"либо перезагрузите конфигурацию, либо проигнорируйте ошибки." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" From 52ac670913b3339256317d349564ed93762f95b2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 11 Apr 2025 09:47:09 -0700 Subject: [PATCH 52/73] CODEOWNERS --- CODEOWNERS | 1 + src/os/i18n.zig | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index 1dbe5af17..e7066ed9f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -164,6 +164,7 @@ /po/ja_JP.UTF-8.po @ghostty-org/ja_JP /po/nb_NO.UTF-8.po @ghostty-org/nb_NO /po/pl_PL.UTF-8.po @ghostty-org/pl_PL +/po/ru_RU.UTF-8.po @ghostty-org/ru_RU /po/uk_UA.UTF-8.po @ghostty-org/uk_UA /po/zh_CN.UTF-8.po @ghostty-org/zh_CN diff --git a/src/os/i18n.zig b/src/os/i18n.zig index d546bbe8f..eab726592 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -23,6 +23,12 @@ const log = std.log.scoped(.i18n); /// /// 3. Most preferred locale for a language without a country code. /// +/// Note for "most common" locales, this is subjective and based on +/// the perceived userbase of Ghostty, which may not be representative +/// of general populations or global language distribution. Also note +/// that ordering may be weird when we first merge a new locale since +/// we don't have a good way to determine this. We can always reorder +/// with some data. pub const locales = [_][:0]const u8{ "zh_CN.UTF-8", "de_DE.UTF-8", From e52aad5deac355e6d4a71dc8e2600d270c971f9f Mon Sep 17 00:00:00 2001 From: Nico Geesink Date: Mon, 24 Mar 2025 15:44:39 +0100 Subject: [PATCH 53/73] Add nl_NL (Dutch) translations --- po/nl_NL.UTF-8.po | 268 ++++++++++++++++++++++++++++++++++++++++++++++ src/os/i18n.zig | 1 + 2 files changed, 269 insertions(+) create mode 100644 po/nl_NL.UTF-8.po diff --git a/po/nl_NL.UTF-8.po b/po/nl_NL.UTF-8.po new file mode 100644 index 000000000..8039a0530 --- /dev/null +++ b/po/nl_NL.UTF-8.po @@ -0,0 +1,268 @@ +# Dutch translations for com.mitchellh.ghostty package. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-03-19 08:28-0700\n" +"PO-Revision-Date: 2025-03-24 15:00+0100\n" +"Last-Translator: \n" +"Language-Team: Dutch \n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "Verander De Titel van De Terminal" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "Laat leeg om de standaard titel te herstellen." + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "Annuleren" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "OK" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "Configuratiefouten" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "" +"Een of meer configuratiefouten zijn gevonden. Bekijk de fouten hieronder, " +"en herlaad uw configuratie of negeer deze fouten." + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "Negeer" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Reload Configuration" +msgstr "Herlaad configuratie" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "Kopiëren" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 +msgid "Paste" +msgstr "Plakken" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "Leegmaken" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "Herstellen" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "Splitsing" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "Verander Titel…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "Splits Naar Boven" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "Splits Naar Beneden" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "Splits Naar Links" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "Splits Naar Rechts" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "Tablad" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:246 +msgid "New Tab" +msgstr "Nieuw Tablad" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "Sluit Tablad" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "Venster" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "Nieuw Venster" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "Sluit Venster" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "Configuratie" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Open Configuration" +msgstr "Open Configuratie" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Terminal Inspector" +msgstr "Terminal Inspecteur" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 +#: src/apprt/gtk/Window.zig:960 +msgid "About Ghostty" +msgstr "Over Ghostty" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +msgid "Quit" +msgstr "Afsluiten" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "Verleen Toegang Tot Klembord" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Een applicatie probeert de inhoud van het klembord te lezen. De huidige " +"inhoud van het klembord wordt hieronder weergegeven." + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "Weigeren" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "Toestaan" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Een applicatie probeert de inhoud van het klembord te wijzigen. De huidige " +"inhoud van het klembord wordt hieronder weergegeven." + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "Waarschuwing: Mogelijk Onveilige Plakactie" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "" +"Het plakken van deze tekst tekst in de terminal is mogelijk gevaarlijk, omdat " +"het lijkt op een commando dat uitgevoerd kan worden." + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Terminal Inspecteur" + +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Gekopieerd naar klembord" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "Afsluiten" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "Wilt U Ghostty Afsluiten?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "Wilt U Dit Venster Afsluiten?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "Wil U Dit Tablad Afsluiten?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "Wilt U Deze Splitsing Afsluiten?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "Alle terminalsessies zullen worden beëindigd." + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "Alle terminalsessies binnen dit venster zullen worden beëindigd." + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "Alle terminalsessies binnen dit tablad zullen worden beëindigd." + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "Alle processen die nu draaien in deze splitsing zullen worden beëindigd." + +#: src/apprt/gtk/Window.zig:200 +msgid "Main Menu" +msgstr "Hoofdmenu" + +#: src/apprt/gtk/Window.zig:221 +msgid "View Open Tabs" +msgstr "Open Tabladen Bekijken" + +#: src/apprt/gtk/Window.zig:295 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ U draait een debug versie van Ghostty! Prestaties zullen minder zijn dan normaal." + +#: src/apprt/gtk/Window.zig:725 +msgid "Reloaded the configuration" +msgstr "De configuratie is herladen" + +#: src/apprt/gtk/Window.zig:941 +msgid "Ghostty Developers" +msgstr "Ghostty Ontwikkelaars" diff --git a/src/os/i18n.zig b/src/os/i18n.zig index eab726592..fd51f1b2e 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -33,6 +33,7 @@ pub const locales = [_][:0]const u8{ "zh_CN.UTF-8", "de_DE.UTF-8", "fr_FR.UTF-8", + "nl_NL.UTF-8", "nb_NO.UTF-8", "ru_RU.UTF-8", "uk_UA.UTF-8", From 7db64b8e347e27b3586b83d4fe723139b9a8e448 Mon Sep 17 00:00:00 2001 From: Nico Geesink Date: Mon, 24 Mar 2025 19:53:51 +0100 Subject: [PATCH 54/73] Add name and don't use title case --- po/nl_NL.UTF-8.po | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/po/nl_NL.UTF-8.po b/po/nl_NL.UTF-8.po index 8039a0530..809423223 100644 --- a/po/nl_NL.UTF-8.po +++ b/po/nl_NL.UTF-8.po @@ -1,7 +1,7 @@ # Dutch translations for com.mitchellh.ghostty package. # Copyright (C) 2025 Mitchell Hashimoto # This file is distributed under the same license as the com.mitchellh.ghostty package. -# , 2025. +# Nico Geesink , 2025. # msgid "" msgstr "" @@ -9,7 +9,7 @@ msgstr "" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2025-03-19 08:28-0700\n" "PO-Revision-Date: 2025-03-24 15:00+0100\n" -"Last-Translator: \n" +"Last-Translator: Nico Geesink \n" "Language-Team: Dutch \n" "Language: nl\n" "MIME-Version: 1.0\n" @@ -19,7 +19,7 @@ msgstr "" #: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 msgid "Change Terminal Title" -msgstr "Verander De Titel van De Terminal" +msgstr "Verander de titel van de terminal" #: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 msgid "Leave blank to restore the default title." @@ -85,27 +85,27 @@ msgstr "Splitsing" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 msgid "Change Title…" -msgstr "Verander Titel…" +msgstr "Verander titel…" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 msgid "Split Up" -msgstr "Splits Naar Boven" +msgstr "Splits naar boven" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 msgid "Split Down" -msgstr "Splits Naar Beneden" +msgstr "Splits naar beneden" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 msgid "Split Left" -msgstr "Splits Naar Links" +msgstr "Splits naar links" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 msgid "Split Right" -msgstr "Splits Naar Rechts" +msgstr "Splits naar rechts" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" @@ -115,12 +115,12 @@ msgstr "Tablad" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 #: src/apprt/gtk/Window.zig:246 msgid "New Tab" -msgstr "Nieuw Tablad" +msgstr "Nieuw tablad" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 msgid "Close Tab" -msgstr "Sluit Tablad" +msgstr "Sluit tablad" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 msgid "Window" @@ -129,12 +129,12 @@ msgstr "Venster" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 msgid "New Window" -msgstr "Nieuw Venster" +msgstr "Nieuw venster" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 msgid "Close Window" -msgstr "Sluit Venster" +msgstr "Sluit venster" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 msgid "Config" @@ -143,11 +143,11 @@ msgstr "Configuratie" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 msgid "Open Configuration" -msgstr "Open Configuratie" +msgstr "Open configuratie" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 msgid "Terminal Inspector" -msgstr "Terminal Inspecteur" +msgstr "Terminal inspecteur" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 #: src/apprt/gtk/Window.zig:960 @@ -161,7 +161,7 @@ msgstr "Afsluiten" #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 msgid "Authorize Clipboard Access" -msgstr "Verleen Toegang Tot Klembord" +msgstr "Verleen toegang tot klembord" #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 msgid "" @@ -191,7 +191,7 @@ msgstr "" #: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 msgid "Warning: Potentially Unsafe Paste" -msgstr "Waarschuwing: Mogelijk Onveilige Plakactie" +msgstr "Waarschuwing: mogelijk onveilige plakactie" #: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 msgid "" @@ -203,7 +203,7 @@ msgstr "" #: src/apprt/gtk/inspector.zig:144 msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty: Terminal Inspecteur" +msgstr "Ghostty: terminal inspecteur" #: src/apprt/gtk/Surface.zig:1243 msgid "Copied to clipboard" @@ -215,19 +215,19 @@ msgstr "Afsluiten" #: src/apprt/gtk/CloseDialog.zig:87 msgid "Quit Ghostty?" -msgstr "Wilt U Ghostty Afsluiten?" +msgstr "Wilt u Ghostty afsluiten?" #: src/apprt/gtk/CloseDialog.zig:88 msgid "Close Window?" -msgstr "Wilt U Dit Venster Afsluiten?" +msgstr "Wilt u dit venster afsluiten?" #: src/apprt/gtk/CloseDialog.zig:89 msgid "Close Tab?" -msgstr "Wil U Dit Tablad Afsluiten?" +msgstr "Wil u dit tablad afsluiten?" #: src/apprt/gtk/CloseDialog.zig:90 msgid "Close Split?" -msgstr "Wilt U Deze Splitsing Afsluiten?" +msgstr "Wilt u deze splitsing afsluiten?" #: src/apprt/gtk/CloseDialog.zig:96 msgid "All terminal sessions will be terminated." @@ -251,7 +251,7 @@ msgstr "Hoofdmenu" #: src/apprt/gtk/Window.zig:221 msgid "View Open Tabs" -msgstr "Open Tabladen Bekijken" +msgstr "Open tabladen bekijken" #: src/apprt/gtk/Window.zig:295 msgid "" @@ -265,4 +265,4 @@ msgstr "De configuratie is herladen" #: src/apprt/gtk/Window.zig:941 msgid "Ghostty Developers" -msgstr "Ghostty Ontwikkelaars" +msgstr "Ghostty ontwikkelaars" From cd6a8f6a65dc3682ddeb66518f96a9648faded0e Mon Sep 17 00:00:00 2001 From: Nico Geesink Date: Wed, 26 Mar 2025 21:44:00 +0100 Subject: [PATCH 55/73] Fix typo --- po/nl_NL.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/nl_NL.UTF-8.po b/po/nl_NL.UTF-8.po index 809423223..abf3ea238 100644 --- a/po/nl_NL.UTF-8.po +++ b/po/nl_NL.UTF-8.po @@ -198,7 +198,7 @@ msgid "" "Pasting this text into the terminal may be dangerous as it looks like some " "commands may be executed." msgstr "" -"Het plakken van deze tekst tekst in de terminal is mogelijk gevaarlijk, omdat " +"Het plakken van deze tekst in de terminal is mogelijk gevaarlijk, omdat " "het lijkt op een commando dat uitgevoerd kan worden." #: src/apprt/gtk/inspector.zig:144 From 960fcc275f0775009a0e94ed7bca523de2984019 Mon Sep 17 00:00:00 2001 From: Nico Geesink Date: Thu, 27 Mar 2025 20:41:50 +0100 Subject: [PATCH 56/73] Fix typos and make sentences more fluent --- po/nl_NL.UTF-8.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/po/nl_NL.UTF-8.po b/po/nl_NL.UTF-8.po index abf3ea238..b64e5ee65 100644 --- a/po/nl_NL.UTF-8.po +++ b/po/nl_NL.UTF-8.po @@ -43,7 +43,7 @@ msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." msgstr "" -"Een of meer configuratiefouten zijn gevonden. Bekijk de fouten hieronder, " +"Er zijn een of meer configuratiefouten gevonden. Bekijk de onderstaande fouten " "en herlaad uw configuratie of negeer deze fouten." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 @@ -80,7 +80,7 @@ msgstr "Herstellen" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 msgid "Split" -msgstr "Splitsing" +msgstr "Splitsen" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 @@ -223,7 +223,7 @@ msgstr "Wilt u dit venster afsluiten?" #: src/apprt/gtk/CloseDialog.zig:89 msgid "Close Tab?" -msgstr "Wil u dit tablad afsluiten?" +msgstr "Wilt u dit tablad afsluiten?" #: src/apprt/gtk/CloseDialog.zig:90 msgid "Close Split?" From 059caef1183950bb5db59f961e4b2b6bd3872c6e Mon Sep 17 00:00:00 2001 From: Nico Geesink Date: Thu, 27 Mar 2025 21:10:45 +0100 Subject: [PATCH 57/73] Use informal 'you' and change verander to wijzig --- po/nl_NL.UTF-8.po | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/po/nl_NL.UTF-8.po b/po/nl_NL.UTF-8.po index b64e5ee65..58bac50ee 100644 --- a/po/nl_NL.UTF-8.po +++ b/po/nl_NL.UTF-8.po @@ -19,7 +19,7 @@ msgstr "" #: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 msgid "Change Terminal Title" -msgstr "Verander de titel van de terminal" +msgstr "Titel van de terminal wijzigen" #: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 msgid "Leave blank to restore the default title." @@ -44,7 +44,7 @@ msgid "" "and either reload your configuration or ignore these errors." msgstr "" "Er zijn een of meer configuratiefouten gevonden. Bekijk de onderstaande fouten " -"en herlaad uw configuratie of negeer deze fouten." +"en herlaad je configuratie of negeer deze fouten." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" @@ -85,7 +85,7 @@ msgstr "Splitsen" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 msgid "Change Title…" -msgstr "Verander titel…" +msgstr "Wijzig titel…" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 @@ -215,19 +215,19 @@ msgstr "Afsluiten" #: src/apprt/gtk/CloseDialog.zig:87 msgid "Quit Ghostty?" -msgstr "Wilt u Ghostty afsluiten?" +msgstr "Wil je Ghostty afsluiten?" #: src/apprt/gtk/CloseDialog.zig:88 msgid "Close Window?" -msgstr "Wilt u dit venster afsluiten?" +msgstr "Wil je dit venster afsluiten?" #: src/apprt/gtk/CloseDialog.zig:89 msgid "Close Tab?" -msgstr "Wilt u dit tablad afsluiten?" +msgstr "Wil je dit tablad afsluiten?" #: src/apprt/gtk/CloseDialog.zig:90 msgid "Close Split?" -msgstr "Wilt u deze splitsing afsluiten?" +msgstr "Wil je deze splitsing afsluiten?" #: src/apprt/gtk/CloseDialog.zig:96 msgid "All terminal sessions will be terminated." @@ -257,7 +257,7 @@ msgstr "Open tabladen bekijken" msgid "" "⚠️ You're running a debug build of Ghostty! Performance will be degraded." msgstr "" -"⚠️ U draait een debug versie van Ghostty! Prestaties zullen minder zijn dan normaal." +"⚠️ Je draait een debug versie van Ghostty! Prestaties zullen minder zijn dan normaal." #: src/apprt/gtk/Window.zig:725 msgid "Reloaded the configuration" From b0b09bf034e3c62bee2928fede4f638abcfb20a3 Mon Sep 17 00:00:00 2001 From: Nico Geesink Date: Wed, 2 Apr 2025 21:03:01 +0200 Subject: [PATCH 58/73] Fix spell errors --- po/nl_NL.UTF-8.po | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/po/nl_NL.UTF-8.po b/po/nl_NL.UTF-8.po index 58bac50ee..6ebea478b 100644 --- a/po/nl_NL.UTF-8.po +++ b/po/nl_NL.UTF-8.po @@ -43,7 +43,7 @@ msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." msgstr "" -"Er zijn een of meer configuratiefouten gevonden. Bekijk de onderstaande fouten " +"Er zijn één of meer configuratiefouten gevonden. Bekijk de onderstaande fouten " "en herlaad je configuratie of negeer deze fouten." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 @@ -109,18 +109,18 @@ msgstr "Splits naar rechts" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" -msgstr "Tablad" +msgstr "Tabblad" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 #: src/apprt/gtk/Window.zig:246 msgid "New Tab" -msgstr "Nieuw tablad" +msgstr "Nieuw tabblad" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 msgid "Close Tab" -msgstr "Sluit tablad" +msgstr "Sluit tabblad" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 msgid "Window" @@ -223,7 +223,7 @@ msgstr "Wil je dit venster afsluiten?" #: src/apprt/gtk/CloseDialog.zig:89 msgid "Close Tab?" -msgstr "Wil je dit tablad afsluiten?" +msgstr "Wil je dit tabblad afsluiten?" #: src/apprt/gtk/CloseDialog.zig:90 msgid "Close Split?" @@ -239,7 +239,7 @@ msgstr "Alle terminalsessies binnen dit venster zullen worden beëindigd." #: src/apprt/gtk/CloseDialog.zig:98 msgid "All terminal sessions in this tab will be terminated." -msgstr "Alle terminalsessies binnen dit tablad zullen worden beëindigd." +msgstr "Alle terminalsessies binnen dit tabblad zullen worden beëindigd." #: src/apprt/gtk/CloseDialog.zig:99 msgid "The currently running process in this split will be terminated." @@ -251,7 +251,7 @@ msgstr "Hoofdmenu" #: src/apprt/gtk/Window.zig:221 msgid "View Open Tabs" -msgstr "Open tabladen bekijken" +msgstr "Open tabbladen bekijken" #: src/apprt/gtk/Window.zig:295 msgid "" From 14cac67b98040ce8d7a1323987cbe7904780716b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 11 Apr 2025 09:51:14 -0700 Subject: [PATCH 59/73] CODEOWNERS --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index e7066ed9f..a7992806a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -163,6 +163,7 @@ /po/id_ID.UTF-8.po @ghostty-org/id_ID /po/ja_JP.UTF-8.po @ghostty-org/ja_JP /po/nb_NO.UTF-8.po @ghostty-org/nb_NO +/po/nl_NL.UTF-8.po @ghostty-org/nl_NL /po/pl_PL.UTF-8.po @ghostty-org/pl_PL /po/ru_RU.UTF-8.po @ghostty-org/ru_RU /po/uk_UA.UTF-8.po @ghostty-org/uk_UA From 913c6dc7dfc17e6385f1922c406383f5e00808a3 Mon Sep 17 00:00:00 2001 From: Emir SARI Date: Mon, 24 Mar 2025 22:18:41 +0300 Subject: [PATCH 60/73] feat: Add Turkish translations --- CODEOWNERS | 1 + po/tr_TR.UTF-8.po | 270 ++++++++++++++++++++++++++++++++++++++++++++++ src/os/i18n.zig | 3 +- 3 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 po/tr_TR.UTF-8.po diff --git a/CODEOWNERS b/CODEOWNERS index a7992806a..7cd2c235b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -166,6 +166,7 @@ /po/nl_NL.UTF-8.po @ghostty-org/nl_NL /po/pl_PL.UTF-8.po @ghostty-org/pl_PL /po/ru_RU.UTF-8.po @ghostty-org/ru_RU +/po/tr_TR.UTF-8.po @ghostty-org/tr_TR /po/uk_UA.UTF-8.po @ghostty-org/uk_UA /po/zh_CN.UTF-8.po @ghostty-org/zh_CN diff --git a/po/tr_TR.UTF-8.po b/po/tr_TR.UTF-8.po new file mode 100644 index 000000000..cee17a6a1 --- /dev/null +++ b/po/tr_TR.UTF-8.po @@ -0,0 +1,270 @@ +# Turkish translations for com.mitchellh.ghostty package. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# Emir SARI , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-03-19 08:54-0700\n" +"PO-Revision-Date: 2025-03-24 22:01+0300\n" +"Last-Translator: Emir SARI \n" +"Language-Team: Turkish\n" +"Language: tr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "Uçbirim Başlığını Değiştir" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "Öntanımlı başlığı geri yüklemek için boş bırakın." + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "İptal" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "Tamam" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "Yapılandırma Hataları" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "" +"Bir veya daha fazla yapılandırma hatası bulundu. Lütfen aşağıdaki hataları " +"gözden geçirin ve ardından ya yapılandırmanızı yeniden yükleyin ya da bu " +"hataları yok sayın." + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "Yok Say" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Reload Configuration" +msgstr "Yapılandırmayı Yeniden Yükle" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "Kopyala" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 +msgid "Paste" +msgstr "Yapıştır" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "Temizle" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "Sıfırla" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "Böl" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "Başlığı Değiştir…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "Yukarı Doğru Böl" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "Aşağı Doğru Böl" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "Sola Doğru Böl" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "Sağa Doğru Böl" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "Sekme" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:246 +msgid "New Tab" +msgstr "Yeni Sekme" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "Sekmeyi Kapat" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "Pencere" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "Yeni Pencere" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "Pencereyi Kapat" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "Yapılandırma" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Open Configuration" +msgstr "Yapılandırmayı Aç" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Terminal Inspector" +msgstr "Uçbirim Denetçisi" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 +#: src/apprt/gtk/Window.zig:960 +msgid "About Ghostty" +msgstr "Ghostty Hakkında" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +msgid "Quit" +msgstr "Çık" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "Pano Erişimine İzin Ver" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Bir uygulama panodan okumaya çalışıyor. Geçerli pano içeriği aşağıda " +"gösterilmektedir." + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "Reddet" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "İzin Ver" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Bir uygulama panoya yazmaya çalışıyor. Geçerli pano içeriği aşağıda " +"gösterilmektedir." + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "Uyarı: Tehlikeli Olabilecek Yapıştırma" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "" +"Bu metni uçbirime yapıştırmak tehlikeli olabilir; çünkü bir komut " +"yürütülebilecekmiş gibi duruyor." + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Uçbirim Denetçisi" + +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Panoya kopyalandı" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "Kapat" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "Ghostty’den Çık?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "Pencereyi Kapat?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "Sekmeyi Kapat?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "Bölmeyi Kapat?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "Tüm uçbirim oturumları sonlandırılacaktır." + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "Bu penceredeki tüm uçbirim oturumları sonlandırılacaktır." + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "Bu sekmedeki tüm uçbirim oturumları sonlandırılacaktır." + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "Bu bölmedeki şu anda çalışan süreç sonlandırılacaktır." + +#: src/apprt/gtk/Window.zig:200 +msgid "Main Menu" +msgstr "Ana Menü" + +#: src/apprt/gtk/Window.zig:221 +msgid "View Open Tabs" +msgstr "Açık Sekmeleri Görüntüle" + +#: src/apprt/gtk/Window.zig:295 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Ghostty’nin hata ayıklama amaçlı yapılmış bir sürümünü kullanıyorsunuz! " +"Başarım normale göre daha düşük olacaktır." + +#: src/apprt/gtk/Window.zig:725 +msgid "Reloaded the configuration" +msgstr "Yapılandırma yeniden yüklendi" + +#: src/apprt/gtk/Window.zig:941 +msgid "Ghostty Developers" +msgstr "Ghostty Geliştiricileri" diff --git a/src/os/i18n.zig b/src/os/i18n.zig index fd51f1b2e..81fcb35eb 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -33,12 +33,13 @@ pub const locales = [_][:0]const u8{ "zh_CN.UTF-8", "de_DE.UTF-8", "fr_FR.UTF-8", + "ja_JP.UTF-8", "nl_NL.UTF-8", "nb_NO.UTF-8", "ru_RU.UTF-8", "uk_UA.UTF-8", "pl_PL.UTF-8", - "ja_JP.UTF-8", + "tr_TR.UTF-8", "id_ID.UTF-8", }; From e5de0008950141a670312c61e054cdca9373667a Mon Sep 17 00:00:00 2001 From: MiguelElGallo <60221874+MiguelElGallo@users.noreply.github.com> Date: Fri, 28 Mar 2025 16:23:01 +0200 Subject: [PATCH 61/73] add Spanish translations (419 = Latin America) for com.mitchellh.ghostty package --- po/es_419.UTF-8.po | 267 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 po/es_419.UTF-8.po diff --git a/po/es_419.UTF-8.po b/po/es_419.UTF-8.po new file mode 100644 index 000000000..0cbd05d1b --- /dev/null +++ b/po/es_419.UTF-8.po @@ -0,0 +1,267 @@ +# Spanish translations for com.mitchellh.ghostty package. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# Miguel Peredo , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-03-19 08:54-0700\n" +"PO-Revision-Date: 2025-03-28 16:15+0200\n" +"Last-Translator: Miguel Peredo \n" +"Language-Team: Spanish \n" +"Language: es_419\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "Cambiar el título de la terminal" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "Dejar en blanco para restaurar el título predeterminado." + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "Cancelar" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "Aceptar" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "Errores de configuración" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "" +"Se encontraron uno o más errores de configuración. Por favor revise los errores a continuación, " +"y recargue su configuración o ignore estos errores." + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "Ignorar" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Reload Configuration" +msgstr "Recargar configuración" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "Copiar" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 +msgid "Paste" +msgstr "Pegar" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "Limpiar" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "Reiniciar" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "Dividir" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "Cambiar título…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "Dividir arriba" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "Dividir abajo" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "Dividir a la izquierda" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "Dividir a la derecha" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "Pestaña" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:246 +msgid "New Tab" +msgstr "Nueva pestaña" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "Cerrar pestaña" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "Ventana" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "Nueva ventana" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "Cerrar ventana" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "Configuración" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Open Configuration" +msgstr "Abrir configuración" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Terminal Inspector" +msgstr "Inspector de la terminal" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 +#: src/apprt/gtk/Window.zig:960 +msgid "About Ghostty" +msgstr "Acerca de Ghostty" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +msgid "Quit" +msgstr "Salir" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "Autorizar acceso al portapapeles" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Una aplicación está intentando leer desde el portapapeles. El contenido " +"actual del portapapeles se muestra a continuación." + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "Denegar" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "Permitir" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Una aplicación está intentando escribir en el portapapeles. El contenido " +"actual del portapapeles se muestra a continuación." + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "Advertencia: Pegado potencialmente inseguro" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "" +"Pegar este texto en la terminal puede ser peligroso ya que parece que " +"algunos comandos podrían ejecutarse." + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Inspector de la terminal" + +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Copiado al portapapeles" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "Cerrar" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "¿Salir de Ghostty?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "¿Cerrar ventana?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "¿Cerrar pestaña?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "¿Cerrar división?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "Todas las sesiones de terminal serán terminadas." + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "Todas las sesiones de terminal en esta ventana serán terminadas." + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "Todas las sesiones de terminal en esta pestaña serán terminadas." + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "El proceso actualmente en ejecución en esta división será terminado." + +#: src/apprt/gtk/Window.zig:200 +msgid "Main Menu" +msgstr "Menú principal" + +#: src/apprt/gtk/Window.zig:221 +msgid "View Open Tabs" +msgstr "Ver pestañas abiertas" + +#: src/apprt/gtk/Window.zig:295 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "⚠️ Está ejecutando una versión de depuración de Ghostty. El rendimiento no será óptimo." + +#: src/apprt/gtk/Window.zig:725 +msgid "Reloaded the configuration" +msgstr "Configuración recargada" + +#: src/apprt/gtk/Window.zig:941 +msgid "Ghostty Developers" +msgstr "Desarrolladores de Ghostty" From a9f9abd6154720d0584885322c4fac6e3159a804 Mon Sep 17 00:00:00 2001 From: MiguelElGallo <60221874+MiguelElGallo@users.noreply.github.com> Date: Fri, 28 Mar 2025 16:47:39 +0200 Subject: [PATCH 62/73] add Spanish (Latin America) locale support --- src/os/i18n.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/os/i18n.zig b/src/os/i18n.zig index fd51f1b2e..6e62c52b6 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -40,6 +40,7 @@ pub const locales = [_][:0]const u8{ "pl_PL.UTF-8", "ja_JP.UTF-8", "id_ID.UTF-8", + "es_BO.UTF-8", }; /// Set for faster membership lookup of locales. From 5bbed046f6c7aa2156b6850aa7b54af68fc5539a Mon Sep 17 00:00:00 2001 From: MiguelElGallo <60221874+MiguelElGallo@users.noreply.github.com> Date: Fri, 28 Mar 2025 17:49:34 +0200 Subject: [PATCH 63/73] add Spanish (Bolivia) translations and locale support --- CODEOWNERS | 1 + po/{es_419.UTF-8.po => es_BO.UTF-8.po} | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) rename po/{es_419.UTF-8.po => es_BO.UTF-8.po} (99%) diff --git a/CODEOWNERS b/CODEOWNERS index a7992806a..05dab45f9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -159,6 +159,7 @@ /po/README_TRANSLATORS.md @ghostty-org/localization /po/com.mitchellh.ghostty.pot @ghostty-org/localization /po/de_DE.UTF-8.po @ghostty-org/de_DE +/po/es_BO.UTF-8.po @ghostty-org/es_BO /po/fr_FR.UTF-8.po @ghostty-org/fr_FR /po/id_ID.UTF-8.po @ghostty-org/id_ID /po/ja_JP.UTF-8.po @ghostty-org/ja_JP diff --git a/po/es_419.UTF-8.po b/po/es_BO.UTF-8.po similarity index 99% rename from po/es_419.UTF-8.po rename to po/es_BO.UTF-8.po index 0cbd05d1b..339ff54c4 100644 --- a/po/es_419.UTF-8.po +++ b/po/es_BO.UTF-8.po @@ -8,10 +8,10 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2025-03-19 08:54-0700\n" -"PO-Revision-Date: 2025-03-28 16:15+0200\n" +"PO-Revision-Date: 2025-03-28 17:46+0200\n" "Last-Translator: Miguel Peredo \n" "Language-Team: Spanish \n" -"Language: es_419\n" +"Language: es_BO\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" From c19f2aa1bc4f0b3ac0f7cb4eb3883b1977882acd Mon Sep 17 00:00:00 2001 From: g <199296466+asdkoasak@users.noreply.github.com> Date: Fri, 28 Mar 2025 15:00:16 -0300 Subject: [PATCH 64/73] Add pt-BR translations to ghostty --- po/pt_BR.utf8.po | 269 +++++++++++++++++++++++++++++++++++++++++++++++ src/os/i18n.zig | 1 + 2 files changed, 270 insertions(+) create mode 100644 po/pt_BR.utf8.po diff --git a/po/pt_BR.utf8.po b/po/pt_BR.utf8.po new file mode 100644 index 000000000..b6d38948f --- /dev/null +++ b/po/pt_BR.utf8.po @@ -0,0 +1,269 @@ +# Portuguese translations for com.mitchellh.ghostty package +# Traduções em português brasileiro para o pacote com.mitchellh.ghostty. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# itz , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-03-19 08:54-0700\n" +"PO-Revision-Date: 2025-03-28 11:04-0300\n" +"Last-Translator: itz \n" +"Language-Team: Brazilian Portuguese \n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "Mudar título do Terminal" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "Deixe em branco para restaurar o título original." + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "Cancelar" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "OK" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "Erros de configuração" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "" +"Um ou mais erros de configurações encontrados. Por favor revise os erros abaixo, " +"e ou recarregue as suas configurações, ou ignore esses erros." + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "Ignorar" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Reload Configuration" +msgstr "Recarregar Configuração" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "Copiar" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 +msgid "Paste" +msgstr "Colar" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "Limpar" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "Reiniciar" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "Dividir" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "Mudar Título..." + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "Dividir Cima" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "Dividir Baixo" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "Dividir Esquerda" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "Dividir Direita" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "Aba" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:246 +msgid "New Tab" +msgstr "Nova Aba" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "Fechar Aba" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "Janela" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "Nova Janela" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "Fechar Janela" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "Configurar" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Open Configuration" +msgstr "Abrir Configuração" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Terminal Inspector" +msgstr "Inspetor de Terminal" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 +#: src/apprt/gtk/Window.zig:960 +msgid "About Ghostty" +msgstr "Sobre Ghostty" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +msgid "Quit" +msgstr "Sair" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "Autorizar Acesso a Área de Transferência" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Uma aplicação está tentando ler da área de transferência. O conteúdo " +"atual da área de transferência está aparecendo abaixo." + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "Negar" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "Permitir" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Uma aplicação está tentando escrever na área de transferência. O conteúdo " +"atual da área de transferência está aparecendo abaixo." + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "Aviso: Conteúdo Potencialmente Inseguro" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "" +"Colar esse texto em um terminal pode ser perigoso, pois parece que alguns " +"comandos podem ser executados." + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Inspetor de Terminal" + +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Copiado para a área de transferência" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "Fechar" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "Fechar Ghostty?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "Fechar Janela?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "Fechar Aba?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "Fechar Divisão?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "Todas as sessões de terminal serão finalizadas." + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "Todas as sessões de terminal nessa janela serão finalizadas." + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "Todas as sessões de terminal nessa aba serão finalizadas." + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "O processo atual rodando nessa divisão será finalizado." + +#: src/apprt/gtk/Window.zig:200 +msgid "Main Menu" +msgstr "Menu Principal" + +#: src/apprt/gtk/Window.zig:221 +msgid "View Open Tabs" +msgstr "Visualizar Abas Abertas" + +#: src/apprt/gtk/Window.zig:295 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "⚠️ Você está rodando uma build de debug do Ghostty! O desempenho será afetado." + +#: src/apprt/gtk/Window.zig:725 +msgid "Reloaded the configuration" +msgstr "Configuração recarregada" + +#: src/apprt/gtk/Window.zig:941 +msgid "Ghostty Developers" +msgstr "Desenvolvedores Ghostty" diff --git a/src/os/i18n.zig b/src/os/i18n.zig index 7cc5a8309..c1ff9bd4b 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -42,6 +42,7 @@ pub const locales = [_][:0]const u8{ "tr_TR.UTF-8", "id_ID.UTF-8", "es_BO.UTF-8", + "pt_BR.UTF-8", }; /// Set for faster membership lookup of locales. From 63ccdf2cff513a3d5174970a116946a8746377da Mon Sep 17 00:00:00 2001 From: g <199296466+asdkoasak@users.noreply.github.com> Date: Mon, 31 Mar 2025 11:16:42 -0300 Subject: [PATCH 65/73] fix in capital letters --- po/pt_BR.utf8.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/po/pt_BR.utf8.po b/po/pt_BR.utf8.po index b6d38948f..78fa6c3a8 100644 --- a/po/pt_BR.utf8.po +++ b/po/pt_BR.utf8.po @@ -2,7 +2,7 @@ # Traduções em português brasileiro para o pacote com.mitchellh.ghostty. # Copyright (C) 2025 Mitchell Hashimoto # This file is distributed under the same license as the com.mitchellh.ghostty package. -# itz , 2025. +# Gustavo Peres , 2025. # msgid "" msgstr "" @@ -10,7 +10,7 @@ msgstr "" "Report-Msgid-Bugs-To: m@mitchellh.com\n" "POT-Creation-Date: 2025-03-19 08:54-0700\n" "PO-Revision-Date: 2025-03-28 11:04-0300\n" -"Last-Translator: itz \n" +"Last-Translator: Gustavo Peres \n" "Language-Team: Brazilian Portuguese \n" "Language: pt_BR\n" @@ -38,7 +38,7 @@ msgstr "OK" #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 msgid "Configuration Errors" -msgstr "Erros de configuração" +msgstr "Erros de Configuração" #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 msgid "" From f794afe2d85a379a14c290c2e820e311741d7d50 Mon Sep 17 00:00:00 2001 From: g <199296466+asdkoasak@users.noreply.github.com> Date: Mon, 31 Mar 2025 11:25:30 -0300 Subject: [PATCH 66/73] standard file extension name --- po/{pt_BR.utf8.po => pt_BR.UTF-8.po} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename po/{pt_BR.utf8.po => pt_BR.UTF-8.po} (100%) diff --git a/po/pt_BR.utf8.po b/po/pt_BR.UTF-8.po similarity index 100% rename from po/pt_BR.utf8.po rename to po/pt_BR.UTF-8.po From e31c8e09ed4d2589e59a13aa75b9849e1d3170f7 Mon Sep 17 00:00:00 2001 From: Gustavo <199296466+gpd0@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:47:03 -0300 Subject: [PATCH 67/73] Update po/pt_BR.UTF-8.po Co-authored-by: Kat <65649991+00-kat@users.noreply.github.com> --- po/pt_BR.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/pt_BR.UTF-8.po b/po/pt_BR.UTF-8.po index 78fa6c3a8..44f7df740 100644 --- a/po/pt_BR.UTF-8.po +++ b/po/pt_BR.UTF-8.po @@ -87,7 +87,7 @@ msgstr "Dividir" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 msgid "Change Title…" -msgstr "Mudar Título..." +msgstr "Mudar Título…" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 From 11f5797a91436fcdc716cc240b87ff37f04d12ee Mon Sep 17 00:00:00 2001 From: g <199296466+asdkoasak@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:56:07 -0300 Subject: [PATCH 68/73] fix in lower case when required --- po/pt_BR.UTF-8.po | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/po/pt_BR.UTF-8.po b/po/pt_BR.UTF-8.po index 44f7df740..eca4ce3dc 100644 --- a/po/pt_BR.UTF-8.po +++ b/po/pt_BR.UTF-8.po @@ -38,7 +38,7 @@ msgstr "OK" #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 msgid "Configuration Errors" -msgstr "Erros de Configuração" +msgstr "Erros de configuração" #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 msgid "" @@ -87,27 +87,27 @@ msgstr "Dividir" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 msgid "Change Title…" -msgstr "Mudar Título…" +msgstr "Mudar título…" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 msgid "Split Up" -msgstr "Dividir Cima" +msgstr "Dividir cima" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 msgid "Split Down" -msgstr "Dividir Baixo" +msgstr "Dividir baixo" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 msgid "Split Left" -msgstr "Dividir Esquerda" +msgstr "Dividir esquerda" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 msgid "Split Right" -msgstr "Dividir Direita" +msgstr "Dividir direita" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" @@ -117,12 +117,12 @@ msgstr "Aba" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 #: src/apprt/gtk/Window.zig:246 msgid "New Tab" -msgstr "Nova Aba" +msgstr "Nova aba" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 msgid "Close Tab" -msgstr "Fechar Aba" +msgstr "Fechar aba" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 msgid "Window" @@ -131,12 +131,12 @@ msgstr "Janela" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 msgid "New Window" -msgstr "Nova Janela" +msgstr "Nova janela" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 msgid "Close Window" -msgstr "Fechar Janela" +msgstr "Fechar janela" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 msgid "Config" @@ -145,11 +145,11 @@ msgstr "Configurar" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 msgid "Open Configuration" -msgstr "Abrir Configuração" +msgstr "Abrir configuração" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 msgid "Terminal Inspector" -msgstr "Inspetor de Terminal" +msgstr "Inspetor de terminal" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 #: src/apprt/gtk/Window.zig:960 @@ -163,7 +163,7 @@ msgstr "Sair" #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 msgid "Authorize Clipboard Access" -msgstr "Autorizar Acesso a Área de Transferência" +msgstr "Autorizar acesso a área de transferência" #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 msgid "" @@ -193,7 +193,7 @@ msgstr "" #: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 msgid "Warning: Potentially Unsafe Paste" -msgstr "Aviso: Conteúdo Potencialmente Inseguro" +msgstr "Aviso: Conteúdo potencialmente inseguro" #: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 msgid "" @@ -205,7 +205,7 @@ msgstr "" #: src/apprt/gtk/inspector.zig:144 msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty: Inspetor de Terminal" +msgstr "Ghostty: Inspetor de terminal" #: src/apprt/gtk/Surface.zig:1243 msgid "Copied to clipboard" @@ -221,15 +221,15 @@ msgstr "Fechar Ghostty?" #: src/apprt/gtk/CloseDialog.zig:88 msgid "Close Window?" -msgstr "Fechar Janela?" +msgstr "Fechar janela?" #: src/apprt/gtk/CloseDialog.zig:89 msgid "Close Tab?" -msgstr "Fechar Aba?" +msgstr "Fechar aba?" #: src/apprt/gtk/CloseDialog.zig:90 msgid "Close Split?" -msgstr "Fechar Divisão?" +msgstr "Fechar divisão?" #: src/apprt/gtk/CloseDialog.zig:96 msgid "All terminal sessions will be terminated." @@ -253,7 +253,7 @@ msgstr "Menu Principal" #: src/apprt/gtk/Window.zig:221 msgid "View Open Tabs" -msgstr "Visualizar Abas Abertas" +msgstr "Visualizar abas abertas" #: src/apprt/gtk/Window.zig:295 msgid "" From 7e67312c61eb736db67f5743e9a0a989b70d35e8 Mon Sep 17 00:00:00 2001 From: g <199296466+asdkoasak@users.noreply.github.com> Date: Fri, 4 Apr 2025 17:35:11 -0300 Subject: [PATCH 69/73] grammar fix and correct form for some phrases --- po/pt_BR.UTF-8.po | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/po/pt_BR.UTF-8.po b/po/pt_BR.UTF-8.po index eca4ce3dc..f9fadce66 100644 --- a/po/pt_BR.UTF-8.po +++ b/po/pt_BR.UTF-8.po @@ -45,8 +45,8 @@ msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." msgstr "" -"Um ou mais erros de configurações encontrados. Por favor revise os erros abaixo, " -"e ou recarregue as suas configurações, ou ignore esses erros." +"Um ou mais erros de configuração encontrados. Por favor revise os erros abaixo, " +"e ou recarregue sua configuração, ou ignore esses erros." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" @@ -56,7 +56,7 @@ msgstr "Ignorar" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 msgid "Reload Configuration" -msgstr "Recarregar Configuração" +msgstr "Recarregar configuração" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 @@ -92,22 +92,22 @@ msgstr "Mudar título…" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 msgid "Split Up" -msgstr "Dividir cima" +msgstr "Dividir para cima" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 msgid "Split Down" -msgstr "Dividir baixo" +msgstr "Dividir para baixo" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 msgid "Split Left" -msgstr "Dividir esquerda" +msgstr "Dividir à esquerda" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 msgid "Split Right" -msgstr "Dividir direita" +msgstr "Dividir à direita" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" @@ -154,7 +154,7 @@ msgstr "Inspetor de terminal" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 #: src/apprt/gtk/Window.zig:960 msgid "About Ghostty" -msgstr "Sobre Ghostty" +msgstr "Sobre o Ghostty" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 msgid "Quit" @@ -163,7 +163,7 @@ msgstr "Sair" #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 msgid "Authorize Clipboard Access" -msgstr "Autorizar acesso a área de transferência" +msgstr "Autorizar acesso à área de transferência" #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 msgid "" @@ -171,7 +171,7 @@ msgid "" "clipboard contents are shown below." msgstr "" "Uma aplicação está tentando ler da área de transferência. O conteúdo " -"atual da área de transferência está aparecendo abaixo." +"atual da área de transferência está sendo exibido abaixo." #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 From 24847293f2cc1917f3666934d6c560fef7c95042 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 11 Apr 2025 10:12:15 -0700 Subject: [PATCH 70/73] update CODEOWNERS --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index 829316dcb..f1b1c9019 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -166,6 +166,7 @@ /po/nb_NO.UTF-8.po @ghostty-org/nb_NO /po/nl_NL.UTF-8.po @ghostty-org/nl_NL /po/pl_PL.UTF-8.po @ghostty-org/pl_PL +/po/pt_BR.UTF-8.po @ghostty-org/pt_BR /po/ru_RU.UTF-8.po @ghostty-org/ru_RU /po/tr_TR.UTF-8.po @ghostty-org/tr_TR /po/uk_UA.UTF-8.po @ghostty-org/uk_UA From a092d7ae42edc4748057d218f7dc4da8ec06c41d Mon Sep 17 00:00:00 2001 From: Francesc Arpi Roca Date: Thu, 20 Mar 2025 21:19:50 +0100 Subject: [PATCH 71/73] i18n: add catalan translations --- CODEOWNERS | 1 + po/ca_ES.UTF-8.po | 269 ++++++++++++++++++++++++++++++++++++++++++++++ src/os/i18n.zig | 1 + 3 files changed, 271 insertions(+) create mode 100644 po/ca_ES.UTF-8.po diff --git a/CODEOWNERS b/CODEOWNERS index f1b1c9019..fa3d73fd3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -158,6 +158,7 @@ # Localization /po/README_TRANSLATORS.md @ghostty-org/localization /po/com.mitchellh.ghostty.pot @ghostty-org/localization +/po/ca_ES.UTF-8.po @ghostty-org/ca_ES /po/de_DE.UTF-8.po @ghostty-org/de_DE /po/es_BO.UTF-8.po @ghostty-org/es_BO /po/fr_FR.UTF-8.po @ghostty-org/fr_FR diff --git a/po/ca_ES.UTF-8.po b/po/ca_ES.UTF-8.po new file mode 100644 index 000000000..dc017500b --- /dev/null +++ b/po/ca_ES.UTF-8.po @@ -0,0 +1,269 @@ +# Catalan translations for com.mitchellh.ghostty package. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# Francesc Arpi , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-03-19 08:28-0700\n" +"PO-Revision-Date: 2025-03-20 08:07+0100\n" +"Last-Translator: Francesc Arpi \n" +"Language-Team: \n" +"Language: ca\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "Canvia el títol del terminal" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "Deixa en blanc per restaurar el títol per defecte." + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "Cancel·la" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "D'acord" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "Errors de configuració" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "" +"S'han trobat un o més errors de configuració. Si us plau, revisa els errors a " +"continuació i torna a carregar la configuració o ignora aquests errors." + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "Ignora" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Reload Configuration" +msgstr "Torna a carregar la configuració" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "Copia" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 +msgid "Paste" +msgstr "Enganxa" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "Neteja" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "Reinicia" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "Divideix" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "Canvia el títol…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "Divideix cap amunt" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "Divideix cap avall" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "Divideix a l'esquerra" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "Divideix a la dreta" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "Pestanya" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:246 +msgid "New Tab" +msgstr "Nova pestanya" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "Tanca la pestanya" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "Finestra" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "Nova finestra" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "Tanca la finestra" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "Configuració" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Open Configuration" +msgstr "Obre la configuració" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Terminal Inspector" +msgstr "Inspector de terminal" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 +#: src/apprt/gtk/Window.zig:960 +msgid "About Ghostty" +msgstr "Sobre Ghostty" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +msgid "Quit" +msgstr "Surt" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "Autoritza l'accés al porta-retalls" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Una aplicació està intentant llegir del porta-retalls. El contingut actual " +"del porta-retalls es mostra a continuació." + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "Denega" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "Permet" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Una aplicació està intentant escriure al porta-retalls. El contingut actual " +"del porta-retalls es mostra a continuació." + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "Avís: Enganxament potencialment insegur" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "" +"Enganxar aquest text al terminal pot ser perillós, ja que sembla que es " +"podrien executar algunes ordres." + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Inspector de terminal" + +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Copiat al porta-retalls" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "Tanca" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "Surt de Ghostty?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "Tanca la finestra?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "Tanca la pestanya?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "Tanca la divisió?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "Totes les sessions del terminal es tancaran." + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "Totes les sessions del terminal en aquesta finestra es tancaran." + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "Totes les sessions del terminal en aquesta pestanya es tancaran." + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "El procés actualment en execució en aquesta divisió es tancarà." + +#: src/apprt/gtk/Window.zig:200 +msgid "Main Menu" +msgstr "Menú principal" + +#: src/apprt/gtk/Window.zig:221 +msgid "View Open Tabs" +msgstr "Mostra les pestanyes obertes" + +#: src/apprt/gtk/Window.zig:295 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Estàs executant una versió de depuració de Ghostty! El rendiment es " +"veurà afectat." + +#: src/apprt/gtk/Window.zig:725 +msgid "Reloaded the configuration" +msgstr "S'ha tornat a carregar la configuració" + +#: src/apprt/gtk/Window.zig:941 +msgid "Ghostty Developers" +msgstr "Desenvolupadors de Ghostty" diff --git a/src/os/i18n.zig b/src/os/i18n.zig index c1ff9bd4b..9c5c054ec 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -30,6 +30,7 @@ const log = std.log.scoped(.i18n); /// we don't have a good way to determine this. We can always reorder /// with some data. pub const locales = [_][:0]const u8{ + "ca_ES.UTF-8", "zh_CN.UTF-8", "de_DE.UTF-8", "fr_FR.UTF-8", From e30feb3bfbf6ce37c581d99c13b471ab92a898d7 Mon Sep 17 00:00:00 2001 From: Francesc Arpi Roca Date: Thu, 20 Mar 2025 21:30:55 +0100 Subject: [PATCH 72/73] i18n: fix string length --- po/ca_ES.UTF-8.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/ca_ES.UTF-8.po b/po/ca_ES.UTF-8.po index dc017500b..ef4160d1b 100644 --- a/po/ca_ES.UTF-8.po +++ b/po/ca_ES.UTF-8.po @@ -54,7 +54,7 @@ msgstr "Ignora" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 msgid "Reload Configuration" -msgstr "Torna a carregar la configuració" +msgstr "Carrega la configuració" #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 From d749e1b87e75f128d0be80dd1ab756e5b08d025c Mon Sep 17 00:00:00 2001 From: Francesc Arpi Roca Date: Fri, 21 Mar 2025 08:05:03 +0100 Subject: [PATCH 73/73] i18n: fix the "deny" catalan translation --- po/ca_ES.UTF-8.po | 2 +- src/os/i18n.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/po/ca_ES.UTF-8.po b/po/ca_ES.UTF-8.po index ef4160d1b..5cbb7efd5 100644 --- a/po/ca_ES.UTF-8.po +++ b/po/ca_ES.UTF-8.po @@ -174,7 +174,7 @@ msgstr "" #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 msgid "Deny" -msgstr "Denega" +msgstr "Denegar" #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 diff --git a/src/os/i18n.zig b/src/os/i18n.zig index 9c5c054ec..5fc376417 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -30,7 +30,6 @@ const log = std.log.scoped(.i18n); /// we don't have a good way to determine this. We can always reorder /// with some data. pub const locales = [_][:0]const u8{ - "ca_ES.UTF-8", "zh_CN.UTF-8", "de_DE.UTF-8", "fr_FR.UTF-8", @@ -44,6 +43,7 @@ pub const locales = [_][:0]const u8{ "id_ID.UTF-8", "es_BO.UTF-8", "pt_BR.UTF-8", + "ca_ES.UTF-8", }; /// Set for faster membership lookup of locales.