mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-04-20 16:38:38 +03:00
os: locale automatically sets LANGUAGE based on macOS preferred
This commit is contained in:
@ -33,7 +33,6 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
language = "zh-Hans"
|
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
@ -6,10 +6,22 @@ const log = std.log.scoped(.i18n);
|
|||||||
/// Supported locales for the application. This must be kept up to date
|
/// Supported locales for the application. This must be kept up to date
|
||||||
/// with the translations available in the `po/` directory; this is used
|
/// with the translations available in the `po/` directory; this is used
|
||||||
/// by our build process as well runtime libghostty APIs.
|
/// by our build process as well runtime libghostty APIs.
|
||||||
pub const locales = [_][]const u8{
|
///
|
||||||
|
/// The order also matters. For incomplete locale information (i.e. only
|
||||||
|
/// a language code available), the first match is used. For example, if
|
||||||
|
/// we know the user requested `zh` but has no region code, then we'd pick
|
||||||
|
/// the first locale that matches `zh`.
|
||||||
|
pub const locales = [_][:0]const u8{
|
||||||
"zh_CN.UTF-8",
|
"zh_CN.UTF-8",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Set for faster membership lookup of locales.
|
||||||
|
pub const locales_map = map: {
|
||||||
|
var kvs: [locales.len]struct { []const u8 } = undefined;
|
||||||
|
for (locales, 0..) |locale, i| kvs[i] = .{locale};
|
||||||
|
break :map std.StaticStringMap(void).initComptime(kvs);
|
||||||
|
};
|
||||||
|
|
||||||
pub const InitError = error{
|
pub const InitError = error{
|
||||||
InvalidResourcesDir,
|
InvalidResourcesDir,
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
@ -40,6 +52,18 @@ pub fn init(resources_dir: []const u8) InitError!void {
|
|||||||
return error.OutOfMemory;
|
return error.OutOfMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finds the closest matching locale for a given language code.
|
||||||
|
pub fn closestLocaleForLanguage(lang: []const u8) ?[:0]const u8 {
|
||||||
|
for (locales) |locale| {
|
||||||
|
const idx = std.mem.indexOfScalar(u8, locale, '_') orelse continue;
|
||||||
|
if (std.mem.eql(u8, locale[0..idx], lang)) {
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Translate a message for the Ghostty domain.
|
/// Translate a message for the Ghostty domain.
|
||||||
pub fn _(msgid: [*:0]const u8) [*:0]const u8 {
|
pub fn _(msgid: [*:0]const u8) [*:0]const u8 {
|
||||||
return dgettext(build_config.bundle_id, msgid);
|
return dgettext(build_config.bundle_id, msgid);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const macos = @import("macos");
|
||||||
const objc = @import("objc");
|
const objc = @import("objc");
|
||||||
const internal_os = @import("main.zig");
|
const internal_os = @import("main.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.os);
|
const log = std.log.scoped(.os_locale);
|
||||||
|
|
||||||
/// Ensure that the locale is set.
|
/// Ensure that the locale is set.
|
||||||
pub fn ensureLocale(alloc: std.mem.Allocator) !void {
|
pub fn ensureLocale(alloc: std.mem.Allocator) !void {
|
||||||
@ -60,7 +61,7 @@ pub fn ensureLocale(alloc: std.mem.Allocator) !void {
|
|||||||
_ = internal_os.setenv("LANG", "en_US.UTF-8");
|
_ = internal_os.setenv("LANG", "en_US.UTF-8");
|
||||||
log.info("setlocale default result={s}", .{v});
|
log.info("setlocale default result={s}", .{v});
|
||||||
return;
|
return;
|
||||||
} else log.err("setlocale failed even with the fallback, uncertain results", .{});
|
} else log.warn("setlocale failed even with the fallback, uncertain results", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This sets the LANG environment variable based on the macOS system
|
/// This sets the LANG environment variable based on the macOS system
|
||||||
@ -71,7 +72,7 @@ fn setLangFromCocoa() void {
|
|||||||
|
|
||||||
// The classes we're going to need.
|
// The classes we're going to need.
|
||||||
const NSLocale = objc.getClass("NSLocale") orelse {
|
const NSLocale = objc.getClass("NSLocale") orelse {
|
||||||
log.err("NSLocale class not found. Locale may be incorrect.", .{});
|
log.warn("NSLocale class not found. Locale may be incorrect.", .{});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -92,16 +93,76 @@ fn setLangFromCocoa() void {
|
|||||||
// Format them into a buffer
|
// Format them into a buffer
|
||||||
var buf: [128]u8 = undefined;
|
var buf: [128]u8 = undefined;
|
||||||
const env_value = std.fmt.bufPrintZ(&buf, "{s}_{s}.UTF-8", .{ z_lang, z_country }) catch |err| {
|
const env_value = std.fmt.bufPrintZ(&buf, "{s}_{s}.UTF-8", .{ z_lang, z_country }) catch |err| {
|
||||||
log.err("error setting locale from system. err={}", .{err});
|
log.warn("error setting locale from system. err={}", .{err});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
log.info("detected system locale={s}", .{env_value});
|
log.info("detected system locale={s}", .{env_value});
|
||||||
|
|
||||||
// Set it onto our environment
|
// Set it onto our environment
|
||||||
if (internal_os.setenv("LANG", env_value) < 0) {
|
if (internal_os.setenv("LANG", env_value) < 0) {
|
||||||
log.err("error setting locale env var", .{});
|
log.warn("error setting locale env var", .{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We also want to set our LANGUAGE for translations. We do this using
|
||||||
|
// NSLocale.preferredLanguages over our system locale since we want to
|
||||||
|
// match our app's preferred languages.
|
||||||
|
language: {
|
||||||
|
const i18n = internal_os.i18n;
|
||||||
|
|
||||||
|
// We need to get our app's preferred languages. These may not
|
||||||
|
// match the system locale (NSLocale.currentLocale).
|
||||||
|
const preferred: *macos.foundation.Array = array: {
|
||||||
|
const ns = NSLocale.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("preferredLanguages"),
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
break :array @ptrCast(ns.value);
|
||||||
|
};
|
||||||
|
for (0..preferred.getCount()) |i| {
|
||||||
|
const str = preferred.getValueAtIndex(macos.foundation.String, i);
|
||||||
|
const c_str = c_str: {
|
||||||
|
const raw = str.cstring(&buf, .utf8) orelse {
|
||||||
|
// I don't think this can happen but if it does then I want
|
||||||
|
// to know about it if a user has translation issues.
|
||||||
|
log.warn("failed to convert a preferred language to UTF-8", .{});
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// We want to strip at "-" since we only care about the language
|
||||||
|
// code, not the region code. i.e. "zh-Hans" -> "zh"
|
||||||
|
const idx = std.mem.indexOfScalar(u8, raw, '-') orelse raw.len;
|
||||||
|
break :c_str raw[0..idx];
|
||||||
|
};
|
||||||
|
|
||||||
|
// If our preferred language is equal to our system language
|
||||||
|
// then we can be done, since the locale above we set everything.
|
||||||
|
if (std.mem.eql(u8, c_str, z_lang)) {
|
||||||
|
log.debug("preferred language matches system locale={s}", .{c_str});
|
||||||
|
break :language;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: there are many improvements that can be made here to make
|
||||||
|
// this more and more robust. For example, we can try to search for
|
||||||
|
// the MOST matching supported locale for translations. Right now
|
||||||
|
// we fall directly back to language code.
|
||||||
|
log.debug("searching for closest matching locale preferred={s}", .{c_str});
|
||||||
|
if (i18n.closestLocaleForLanguage(c_str)) |i18n_locale| {
|
||||||
|
log.info("setting LANGUAGE to closest matching locale={s}", .{i18n_locale});
|
||||||
|
_ = internal_os.setenv("LANGUAGE", i18n_locale);
|
||||||
|
break :language;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No matches or our preferred languages are empty. As a final
|
||||||
|
// try we try to match our system locale.
|
||||||
|
if (i18n.closestLocaleForLanguage(z_lang)) |i18n_locale| {
|
||||||
|
log.info("setting LANGUAGE to closest matching locale={s}", .{i18n_locale});
|
||||||
|
_ = internal_os.setenv("LANGUAGE", i18n_locale);
|
||||||
|
break :language;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const LC_ALL: c_int = 6; // from locale.h
|
const LC_ALL: c_int = 6; // from locale.h
|
||||||
|
Reference in New Issue
Block a user