mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-04-20 00:18:53 +03:00
os: locale automatically sets LANGUAGE based on macOS preferred
This commit is contained in:
@ -33,7 +33,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = "zh-Hans"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "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
|
||||
/// with the translations available in the `po/` directory; this is used
|
||||
/// 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",
|
||||
};
|
||||
|
||||
/// 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{
|
||||
InvalidResourcesDir,
|
||||
OutOfMemory,
|
||||
@ -40,6 +52,18 @@ pub fn init(resources_dir: []const u8) InitError!void {
|
||||
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.
|
||||
pub fn _(msgid: [*:0]const u8) [*:0]const u8 {
|
||||
return dgettext(build_config.bundle_id, msgid);
|
||||
|
@ -1,10 +1,11 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const macos = @import("macos");
|
||||
const objc = @import("objc");
|
||||
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.
|
||||
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");
|
||||
log.info("setlocale default result={s}", .{v});
|
||||
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
|
||||
@ -71,7 +72,7 @@ fn setLangFromCocoa() void {
|
||||
|
||||
// The classes we're going to need.
|
||||
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;
|
||||
};
|
||||
|
||||
@ -92,16 +93,76 @@ fn setLangFromCocoa() void {
|
||||
// Format them into a buffer
|
||||
var buf: [128]u8 = undefined;
|
||||
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;
|
||||
};
|
||||
log.info("detected system locale={s}", .{env_value});
|
||||
|
||||
// Set it onto our environment
|
||||
if (internal_os.setenv("LANG", env_value) < 0) {
|
||||
log.err("error setting locale env var", .{});
|
||||
log.warn("error setting locale env var", .{});
|
||||
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
|
||||
|
Reference in New Issue
Block a user