Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 36 additions & 11 deletions src/apprt/gtk/class/application.zig
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ pub const Application = extern struct {
/// Providers for loading custom stylesheets defined by user
custom_css_providers: std.ArrayListUnmanaged(*gtk.CssProvider) = .empty,

/// A copy of the LANG environment variable that was provided to Ghostty
/// by the system. If this is null, the LANG environment variable did
/// not exist in Ghostty's environment variable.
saved_language: ?[:0]const u8 = null,

pub var offset: c_int = 0;
};

Expand Down Expand Up @@ -249,15 +254,6 @@ pub const Application = extern struct {
gtk_version.logVersion();
adw_version.logVersion();

// Set gettext global domain to be our app so that our unqualified
// translations map to our translations.
internal_os.i18n.initGlobalDomain() catch |err| {
// Failures shuldn't stop application startup. Our app may
// not translate correctly but it should still work. In the
// future we may want to add this to the GUI to show.
log.warn("i18n initialization failed error={}", .{err});
};

// Load our configuration.
var config = CoreConfig.load(alloc) catch |err| err: {
// If we fail to load the configuration, then we should log
Expand All @@ -275,6 +271,27 @@ pub const Application = extern struct {
};
defer config.deinit();

const saved_language: ?[:0]const u8 = saved_language: {
const old_language = old_language: {
const result = (internal_os.getenv(alloc, "LANG") catch break :old_language null) orelse break :old_language null;
defer result.deinit(alloc);
break :old_language alloc.dupeZ(u8, result.value) catch break :old_language null;
};

if (config.language) |language| _ = internal_os.setenv("LANG", language);

break :saved_language old_language;
};

// Set gettext global domain to be our app so that our unqualified
// translations map to our translations.
internal_os.i18n.initGlobalDomain() catch |err| {
// Failures shuldn't stop application startup. Our app may
// not translate correctly but it should still work. In the
// future we may want to add this to the GUI to show.
log.warn("i18n initialization failed error={}", .{err});
};

// Setup our GTK init env vars
setGtkEnv(&config) catch |err| switch (err) {
error.NoSpaceLeft => {
Expand Down Expand Up @@ -374,7 +391,7 @@ pub const Application = extern struct {
// Setup our private state. More setup is done in the init
// callback that GObject calls, but we can't pass this data through
// to there (and we don't need it there directly) so this is here.
const priv = self.private();
const priv: *Private = self.private();
priv.* = .{
.rt_app = rt_app,
.core_app = core_app,
Expand All @@ -383,6 +400,7 @@ pub const Application = extern struct {
.css_provider = css_provider,
.custom_css_providers = .empty,
.global_shortcuts = gobject.ext.newInstance(GlobalShortcuts, .{}),
.saved_language = saved_language,
};

// Signals
Expand Down Expand Up @@ -415,11 +433,12 @@ pub const Application = extern struct {
/// ensures that our memory is cleaned up properly.
pub fn deinit(self: *Self) void {
const alloc = self.allocator();
const priv = self.private();
const priv: *Private = self.private();
priv.config.unref();
priv.winproto.deinit(alloc);
priv.global_shortcuts.unref();
if (priv.transient_cgroup_base) |base| alloc.free(base);
if (priv.saved_language) |language| alloc.free(language);
if (gdk.Display.getDefault()) |display| {
gtk.StyleContext.removeProviderForDisplay(
display,
Expand All @@ -445,6 +464,12 @@ pub const Application = extern struct {
return self.private().core_app.alloc;
}

/// Get the original language that Ghostty was launched with. This returns a
/// pointer to internal memory so it must be copied by callers.
pub fn savedLanguage(self: *Self) ?[:0]const u8 {
return self.private().saved_language;
}

/// Run the application. This is a replacement for `gio.Application.run`
/// because we want more tight control over our event loop so we can
/// integrate it with libghostty.
Expand Down
9 changes: 8 additions & 1 deletion src/apprt/gtk/class/surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1588,10 +1588,17 @@ pub const Surface = extern struct {
}

pub fn defaultTermioEnv(self: *Self) !std.process.EnvMap {
const alloc = Application.default().allocator();
const app = Application.default();
const alloc = app.allocator();
var env = try internal_os.getEnvMap(alloc);
errdefer env.deinit();

if (app.savedLanguage()) |language| {
try env.put("LANG", language);
} else {
env.remove("LANG");
}

// Don't leak these GTK environment variables to child processes.
env.remove("GDK_DEBUG");
env.remove("GDK_DISABLE");
Expand Down
21 changes: 21 additions & 0 deletions src/config/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,27 @@ pub const compatibility = std.StaticStringMap(
.{ "macos-dock-drop-behavior", compatMacOSDockDropBehavior },
});

/// Set Ghostty's graphical user interface language to a language other than the
/// system default language. The language must be fully specified, including the
/// encoding. For example:
///
/// language = de_DE.UTF-8
///
/// will force the strings in Ghostty's graphical user interface to be in German
/// rather than the system default.
///
/// This will not affect the language used by programs run _within_ Ghostty.
/// Those will continue to use the default system language. There are also many
/// non-GUI elements in Ghostty that are not translated - this setting will have
/// no effect on those.
///
/// Warning: This setting cannot be reloaded at runtime. To change the language
/// you must fully restart Ghostty.
///
/// GTK only.
/// Available since 1.3.0.
language: ?[:0]const u8 = null,

/// The font families to use.
///
/// You can generate the list of valid values using the CLI:
Expand Down