1
1
const std = @import ("std" );
2
2
const builtin = @import ("builtin" );
3
3
const assert = std .debug .assert ;
4
+ const macos = @import ("macos" );
4
5
const objc = @import ("objc" );
5
6
const internal_os = @import ("main.zig" );
6
7
7
- const log = std .log .scoped (.os );
8
+ const log = std .log .scoped (.os_locale );
8
9
9
10
/// Ensure that the locale is set.
10
11
pub fn ensureLocale (alloc : std.mem.Allocator ) ! void {
@@ -60,7 +61,7 @@ pub fn ensureLocale(alloc: std.mem.Allocator) !void {
60
61
_ = internal_os .setenv ("LANG" , "en_US.UTF-8" );
61
62
log .info ("setlocale default result={s}" , .{v });
62
63
return ;
63
- } else log .err ("setlocale failed even with the fallback, uncertain results" , .{});
64
+ } else log .warn ("setlocale failed even with the fallback, uncertain results" , .{});
64
65
}
65
66
66
67
/// This sets the LANG environment variable based on the macOS system
@@ -71,7 +72,7 @@ fn setLangFromCocoa() void {
71
72
72
73
// The classes we're going to need.
73
74
const NSLocale = objc .getClass ("NSLocale" ) orelse {
74
- log .err ("NSLocale class not found. Locale may be incorrect." , .{});
75
+ log .warn ("NSLocale class not found. Locale may be incorrect." , .{});
75
76
return ;
76
77
};
77
78
@@ -92,16 +93,76 @@ fn setLangFromCocoa() void {
92
93
// Format them into a buffer
93
94
var buf : [128 ]u8 = undefined ;
94
95
const env_value = std .fmt .bufPrintZ (& buf , "{s}_{s}.UTF-8" , .{ z_lang , z_country }) catch | err | {
95
- log .err ("error setting locale from system. err={}" , .{err });
96
+ log .warn ("error setting locale from system. err={}" , .{err });
96
97
return ;
97
98
};
98
99
log .info ("detected system locale={s}" , .{env_value });
99
100
100
101
// Set it onto our environment
101
102
if (internal_os .setenv ("LANG" , env_value ) < 0 ) {
102
- log .err ("error setting locale env var" , .{});
103
+ log .warn ("error setting locale env var" , .{});
103
104
return ;
104
105
}
106
+
107
+ // We also want to set our LANGUAGE for translations. We do this using
108
+ // NSLocale.preferredLanguages over our system locale since we want to
109
+ // match our app's preferred languages.
110
+ language : {
111
+ const i18n = internal_os .i18n ;
112
+
113
+ // We need to get our app's preferred languages. These may not
114
+ // match the system locale (NSLocale.currentLocale).
115
+ const preferred : * macos.foundation.Array = array : {
116
+ const ns = NSLocale .msgSend (
117
+ objc .Object ,
118
+ objc .sel ("preferredLanguages" ),
119
+ .{},
120
+ );
121
+ break :array @ptrCast (ns .value );
122
+ };
123
+ for (0.. preferred .getCount ()) | i | {
124
+ const str = preferred .getValueAtIndex (macos .foundation .String , i );
125
+ const c_str = c_str : {
126
+ const raw = str .cstring (& buf , .utf8 ) orelse {
127
+ // I don't think this can happen but if it does then I want
128
+ // to know about it if a user has translation issues.
129
+ log .warn ("failed to convert a preferred language to UTF-8" , .{});
130
+ continue ;
131
+ };
132
+
133
+ // We want to strip at "-" since we only care about the language
134
+ // code, not the region code. i.e. "zh-Hans" -> "zh"
135
+ const idx = std .mem .indexOfScalar (u8 , raw , '-' ) orelse raw .len ;
136
+ break :c_str raw [0.. idx ];
137
+ };
138
+
139
+ // If our preferred language is equal to our system language
140
+ // then we can be done, since the locale above we set everything.
141
+ if (std .mem .eql (u8 , c_str , z_lang )) {
142
+ log .debug ("preferred language matches system locale={s}" , .{c_str });
143
+ break :language ;
144
+ }
145
+
146
+ // Note: there are many improvements that can be made here to make
147
+ // this more and more robust. For example, we can try to search for
148
+ // the MOST matching supported locale for translations. Right now
149
+ // we fall directly back to language code.
150
+ log .debug ("searching for closest matching locale preferred={s}" , .{c_str });
151
+ if (i18n .closestLocaleForLanguage (c_str )) | i18n_locale | {
152
+ log .info ("setting LANGUAGE to closest matching locale={s}" , .{i18n_locale });
153
+ _ = internal_os .setenv ("LANGUAGE" , i18n_locale );
154
+ break :language ;
155
+ }
156
+ }
157
+
158
+ // No matches or our preferred languages are empty. As a final
159
+ // try we try to match our system locale.
160
+ if (i18n .closestLocaleForLanguage (z_lang )) | i18n_locale | {
161
+ log .info ("setting LANGUAGE to closest matching locale={s}" , .{i18n_locale });
162
+ _ = internal_os .setenv ("LANGUAGE" , i18n_locale );
163
+ break :language ;
164
+ }
165
+ }
105
166
}
106
167
107
168
const LC_ALL : c_int = 6 ; // from locale.h
0 commit comments