Merge branch 'main' into mk-localization

This commit is contained in:
Andrej Daskalov
2025-04-11 21:47:07 +02:00
committed by GitHub
45 changed files with 3867 additions and 391 deletions

View File

@@ -23,13 +23,28 @@ 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{
"de_DE.UTF-8",
"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",
"mk_MK.UTF-8",
"tr_TR.UTF-8",
"id_ID.UTF-8",
"es_BO.UTF-8",
"pt_BR.UTF-8",
"ca_ES.UTF-8",
};
/// Set for faster membership lookup of locales.
@@ -108,6 +123,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;
@@ -126,6 +144,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 {
@@ -160,6 +202,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.

View File

@@ -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;

View File

@@ -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());
}