summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xpo/compile.py48
-rw-r--r--po/en.po3
-rw-r--r--res/lang/en.binbin15248 -> 15487 bytes
-rw-r--r--res/lang/fi.binbin16677 -> 14726 bytes
-rw-r--r--src/lang.c60
-rw-r--r--src/lang.h4
-rw-r--r--src/ui/documentwidget.c29
-rw-r--r--src/ui/sidebarwidget.c8
8 files changed, 115 insertions, 37 deletions
diff --git a/po/compile.py b/po/compile.py
index cfe837ea..0a0eab91 100755
--- a/po/compile.py
+++ b/po/compile.py
@@ -38,29 +38,46 @@ def unquote(string):
38 38
39def parse_po(src): 39def parse_po(src):
40 messages = [] 40 messages = []
41 is_multi = False 41 is_multi = False # string is multiple lines
42 msg_id, msg_str = None, None 42 is_plural = False
43 msg_id, msg_str, msg_index = None, None, None
43 for line in open(src, 'rt', encoding='utf-8').readlines(): 44 for line in open(src, 'rt', encoding='utf-8').readlines():
44 line = line.strip() 45 line = line.strip()
45 if is_multi: 46 if is_multi:
46 if len(line) == 0: 47 if len(line) == 0 or line[0] != '"':
47 if msg_id: 48 if msg_id:
48 messages.append((msg_id, msg_str)) 49 messages.append((msg_id, msg_str, msg_index))
49 is_multi = False 50 is_multi = False
50 continue 51 if len(line) == 0: continue
51 else: 52 else:
52 msg_str += unquote(line) 53 msg_str += unquote(line)
53 if line.startswith('msgid'): 54 if line.startswith('msgid_plural'):
55 msg_id = unquote(line[12:])
56 is_plural = True
57 elif line.startswith('msgid'):
54 msg_id = unquote(line[6:]) 58 msg_id = unquote(line[6:])
55 elif line == 'msgstr ""': 59 is_plural = False
56 # Multiline string. 60 elif line.startswith('msgstr'):
57 is_multi = True 61 if line[6] == '[':
58 msg_str = '' 62 msg_index = int(line[7])
59 elif line.startswith('msgstr'): 63 line = line[9:]
60 msg_str = unquote(line[7:]) 64 else:
61 if msg_id: 65 msg_index = None
62 messages.append((msg_id, msg_str)) 66 line = line[7:]
63 return messages 67 if line.endswith(' ""'):
68 is_multi = True
69 msg_str = ''
70 else:
71 msg_str = unquote(line)
72 if msg_id:
73 messages.append((msg_id, msg_str, msg_index))
74 # Apply plural indices to ids.
75 pluralized = []
76 for msg_id, msg_str, msg_index in messages:
77 if not msg_index is None:
78 msg_id = f'{msg_id[:-1]}{msg_index}'
79 pluralized.append((msg_id, msg_str))
80 return pluralized
64 81
65 82
66if MODE == 'compile': 83if MODE == 'compile':
@@ -76,6 +93,7 @@ if MODE == 'compile':
76elif MODE == 'new': 93elif MODE == 'new':
77 messages = parse_po('en.po') 94 messages = parse_po('en.po')
78 f = open('new.po', 'wt', encoding='utf-8') 95 f = open('new.po', 'wt', encoding='utf-8')
96 # TODO: plurals
79 for msg_id, _ in messages: 97 for msg_id, _ in messages:
80 print(f'\nmsgid "{msg_id}"\nmsgstr ""\n', file=f) 98 print(f'\nmsgid "{msg_id}"\nmsgstr ""\n', file=f)
81 99
diff --git a/po/en.po b/po/en.po
index 36f30eda..ad7864b3 100644
--- a/po/en.po
+++ b/po/en.po
@@ -1,3 +1,4 @@
1# Alt-text of the preformatted logo.
1msgid "about.logo" 2msgid "about.logo"
2msgstr "ASCII art: the word \"Lagrange\" using a large font" 3msgstr "ASCII art: the word \"Lagrange\" using a large font"
3 4
@@ -73,9 +74,11 @@ msgstr "Find on Page"
73msgid "macos.menu.find" 74msgid "macos.menu.find"
74msgstr "Find" 75msgstr "Find"
75 76
77# Used on iOS. "Files" refers to Apple's iOS app where you can pick an iCloud folder.
76msgid "menu.save.files" 78msgid "menu.save.files"
77msgstr "Save to Files" 79msgstr "Save to Files"
78 80
81# Used on desktop operating systems. "Downloads" refers to the user's configured downloads directory.
79msgid "menu.save.downloads" 82msgid "menu.save.downloads"
80msgstr "Save to Downloads" 83msgstr "Save to Downloads"
81 84
diff --git a/res/lang/en.bin b/res/lang/en.bin
index f8e3480e..427ce70b 100644
--- a/res/lang/en.bin
+++ b/res/lang/en.bin
Binary files differ
diff --git a/res/lang/fi.bin b/res/lang/fi.bin
index ba6ab16e..89af1575 100644
--- a/res/lang/fi.bin
+++ b/res/lang/fi.bin
Binary files differ
diff --git a/src/lang.c b/src/lang.c
index be8ad9d1..983ae3c5 100644
--- a/src/lang.c
+++ b/src/lang.c
@@ -19,12 +19,32 @@ int cmp_MsgStr_(const void *e1, const void *e2) {
19 19
20/*----------------------------------------------------------------------------------------------*/ 20/*----------------------------------------------------------------------------------------------*/
21 21
22enum iPluralType {
23 none_PluralType,
24 notEqualToOne_PluralType,
25 slavic_PluralType,
26};
27
22struct Impl_Lang { 28struct Impl_Lang {
23 iSortedArray *messages; 29 iSortedArray *messages;
30 enum iPluralType pluralType;
24}; 31};
25 32
26static iLang lang_; 33static iLang lang_;
27 34
35static size_t pluralIndex_Lang_(const iLang *d, int n) {
36 switch (d->pluralType) {
37 case notEqualToOne_PluralType:
38 return n != 1;
39 case slavic_PluralType:
40 return n % 10 == 1 && n % 100 != 11 ? 0
41 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1
42 : 2;
43 default:
44 return 0;
45 }
46}
47
28static void clear_Lang_(iLang *d) { 48static void clear_Lang_(iLang *d) {
29 clear_SortedArray(d->messages); 49 clear_SortedArray(d->messages);
30} 50}
@@ -36,6 +56,15 @@ static void load_Lang_(iLang *d, const char *id) {
36 : equal_CStr(id, "ru") ? &blobRu_Embedded 56 : equal_CStr(id, "ru") ? &blobRu_Embedded
37 : equal_CStr(id, "de") ? &blobDe_Embedded 57 : equal_CStr(id, "de") ? &blobDe_Embedded
38 : &blobEn_Embedded; 58 : &blobEn_Embedded;
59 if (data == &blobRu_Embedded) {
60 d->pluralType = slavic_PluralType;
61 }
62// else if (data == &blobZhHans_Embedded) {
63// d->pluralType = none_PluralType;
64// }
65 else {
66 d->pluralType = notEqualToOne_PluralType;
67 }
39 iMsgStr msg; 68 iMsgStr msg;
40 for (const char *ptr = constBegin_Block(data); ptr != constEnd_Block(data); ptr++) { 69 for (const char *ptr = constBegin_Block(data); ptr != constEnd_Block(data); ptr++) {
41 msg.id.start = ptr; 70 msg.id.start = ptr;
@@ -45,6 +74,7 @@ static void load_Lang_(iLang *d, const char *id) {
45 while (*++ptr) {} 74 while (*++ptr) {}
46 msg.str.end = ptr; 75 msg.str.end = ptr;
47 /* Allocate the string. The data has already been sorted. */ 76 /* Allocate the string. The data has already been sorted. */
77 printf("ID:%s\n", msg.id.start);
48 pushBack_Array(&d->messages->values, &msg); 78 pushBack_Array(&d->messages->values, &msg);
49 } 79 }
50} 80}
@@ -87,12 +117,30 @@ iRangecc range_Lang(iRangecc msgId) {
87 return str; 117 return str;
88} 118}
89 119
120const iString *string_Lang(const char *msgId) {
121 return collectNewRange_String(range_Lang(range_CStr(msgId)));
122}
123
90const char *cstr_Lang(const char *msgId) { 124const char *cstr_Lang(const char *msgId) {
91 return range_Lang(range_CStr(msgId)).start; /* guaranteed to be NULL-terminated */ 125 return range_Lang(range_CStr(msgId)).start; /* guaranteed to be NULL-terminated */
92} 126}
93 127
94const iString *string_Lang(const char *msgId) { 128static char *pluralId_Lang_(const iLang *d, const char *msgId, int count) {
95 return collectNewRange_String(range_Lang(range_CStr(msgId))); 129 const size_t len = strlen(msgId);
130 char *pluralId = strdup(msgId);
131 pluralId[len - 1] = '0' + pluralIndex_Lang_(d, count);
132 return pluralId;
133}
134
135const char *cstrCount_Lang(const char *msgId, int count) {
136 iAssert(endsWith_Rangecc(range_CStr(msgId), ".n")); /* by convention */
137 char *pluralId = pluralId_Lang_(&lang_, msgId, count);
138 const char *str = cstr_Lang(pluralId);
139 if (str == pluralId) {
140 str = msgId; /* not found */
141 }
142 free(pluralId);
143 return str;
96} 144}
97 145
98void translate_Lang(iString *textWithIds) { 146void translate_Lang(iString *textWithIds) {
@@ -129,3 +177,11 @@ const char *translateCStr_Lang(const char *textWithIds) {
129 translate_Lang(text); 177 translate_Lang(text);
130 return cstr_String(text); 178 return cstr_String(text);
131} 179}
180
181const char *formatCStr_Lang(const char *formatMsgId, int count) {
182 return format_CStr(cstrCount_Lang(formatMsgId, count), count);
183}
184
185const char *formatCStrs_Lang(const char *formatMsgId, size_t count) {
186 return format_CStr(cstrCount_Lang(formatMsgId, (int) count), count);
187}
diff --git a/src/lang.h b/src/lang.h
index ea71e531..bf4c16d1 100644
--- a/src/lang.h
+++ b/src/lang.h
@@ -13,3 +13,7 @@ const iString * string_Lang (const char *msgId);
13 13
14void translate_Lang (iString *textWithIds); 14void translate_Lang (iString *textWithIds);
15const char * translateCStr_Lang (const char *textWithIds); 15const char * translateCStr_Lang (const char *textWithIds);
16
17const char * cstrCount_Lang (const char *msgId, int count);
18const char * formatCStr_Lang (const char *formatMsgId, int count);
19const char * formatCStrs_Lang (const char *formatMsgId, size_t count);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 3d8d4f12..88016b1c 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -2081,20 +2081,17 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2081 } 2081 }
2082 if (!isEmpty_PtrArray(links)) { 2082 if (!isEmpty_PtrArray(links)) {
2083 if (argLabel_Command(cmd, "confirm")) { 2083 if (argLabel_Command(cmd, "confirm")) {
2084 //const char *plural = size_PtrArray(links) != 1 ? "s" : ""; 2084 const size_t count = size_PtrArray(links);
2085 const iBool isPlural = size_PtrArray(links) != 1;
2086 makeQuestion_Widget( 2085 makeQuestion_Widget(
2087 uiHeading_ColorEscape "${heading.import.bookmarks}", 2086 uiHeading_ColorEscape "${heading.import.bookmarks}",
2088 format_CStr(cstr_Lang(isPlural ? "dlg.import.found.many" : "dlg.import.found"), 2087 formatCStrs_Lang("dlg.import.found.n", count),
2089 size_PtrArray(links)), 2088 (iMenuItem[]){ { "${cancel}", 0, 0, NULL },
2090 (iMenuItem[]){ 2089 { format_CStr(cstrCount_Lang("dlg.import.add.n", count),
2091 { "${cancel}", 0, 0, NULL }, 2090 uiTextAction_ColorEscape,
2092 { format_CStr(cstr_Lang(isPlural ? "dlg.import.add.many" : "dlg.import.add"), 2091 count),
2093 uiTextAction_ColorEscape, 2092 0,
2094 size_PtrArray(links)), 2093 0,
2095 0, 2094 "bookmark.links" } },
2096 0,
2097 "bookmark.links" } },
2098 2); 2095 2);
2099 } 2096 }
2100 else { 2097 else {
@@ -2846,10 +2843,10 @@ static void drawBannerRun_DrawContext_(iDrawContext *d, const iGmRun *run, iInt2
2846 const int days = secondsSince_Time(&oldUntil, &now) / 3600 / 24; 2843 const int days = secondsSince_Time(&oldUntil, &now) / 3600 / 24;
2847 appendCStr_String(&str, "\n"); 2844 appendCStr_String(&str, "\n");
2848 if (days <= 30) { 2845 if (days <= 30) {
2849 appendFormat_String(&str, 2846 appendCStr_String(&str,
2850 cstr_Lang("dlg.certwarn.mayberenewed"), 2847 format_CStr(cstrCount_Lang("dlg.certwarn.mayberenewed.n", days),
2851 cstrCollect_String(format_Date(&exp, "%Y-%m-%d")), 2848 cstrCollect_String(format_Date(&exp, "%Y-%m-%d")),
2852 days); 2849 days));
2853 } 2850 }
2854 else { 2851 else {
2855 appendCStr_String(&str, cstr_Lang("dlg.certwarn.different")); 2852 appendCStr_String(&str, cstr_Lang("dlg.certwarn.different"));
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index 5ff585a5..29b3646a 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -352,7 +352,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
352 "%s", 352 "%s",
353 isActive ? cstr_Lang("ident.using") 353 isActive ? cstr_Lang("ident.using")
354 : isUsed_GmIdentity(ident) 354 : isUsed_GmIdentity(ident)
355 ? format_CStr(cstr_Lang("ident.usedonurls"), size_StringSet(ident->useUrls)) 355 ? formatCStrs_Lang("ident.usedonurls.n", size_StringSet(ident->useUrls))
356 : cstr_Lang("ident.notused")); 356 : cstr_Lang("ident.notused"));
357 const char *expiry = 357 const char *expiry =
358 ident->flags & temporary_GmIdentityFlag 358 ident->flags & temporary_GmIdentityFlag
@@ -705,12 +705,12 @@ static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) {
705 if (i == feeds_SidebarMode && d->numUnreadEntries) { 705 if (i == feeds_SidebarMode && d->numUnreadEntries) {
706 updateText_LabelWidget( 706 updateText_LabelWidget(
707 button, 707 button,
708 collectNewFormat_String("%s " uiTextAction_ColorEscape "%zu%s", 708 collectNewFormat_String("%s " uiTextAction_ColorEscape "%zu%s%s",
709 tightModeLabels_[i], 709 tightModeLabels_[i],
710 d->numUnreadEntries, 710 d->numUnreadEntries,
711 !isTight ? " " : "",
711 !isTight 712 !isTight
712 ? (d->numUnreadEntries == 1 ? " ${sidebar.unread}" 713 ? formatCStrs_Lang("sidebar.unread.n", d->numUnreadEntries)
713 : " ${sidebar.unread.many}")
714 : "")); 714 : ""));
715 } 715 }
716 else { 716 else {