Skip to content

Commit 595586a

Browse files
committed
i18n: use translation table for source strings; shrink .exe
The QLJS_TRANSLATABLE macro creates a translatable_message object containing a const char* and a uint16_t. The uint16_t is an index into a translation table, and the const char* is a pointer to the untranslated C string. This has some problems: * Each translatable_message consumes 10 bytes. These bytes add up quickly in the diagnostics table. * Each translatable_message requires a relocation entry (assuming position-independent code generation), so it actually consumes more than 10 bytes. * In the future, I want to compress the translation string table [1]. This compression would not affect untranslated strings. Change the QLJS_TRANSLATABLE macro to not store a const char*. Instead, put the untranslated string into the translation table. This commit reduces the size of the CLI by a dozen or two kilobytes: file | before | after | change ----------------------------------------------+---------+---------+-------- 2.4.0-1_amd64.deb::quick-lint-js | 787568 | 771184 | -2% manual/windows-arm.zip::quick-lint-js.exe | 863744 | 861696 | 0% manual/windows-arm64.zip::quick-lint-js.exe | 1017856 | 1012224 | -1% manual/windows-x86.zip::quick-lint-js.exe | 1165838 | 1163790 | 0% manual/windows.zip::quick-lint-js.exe | 1185280 | 1177088 | -1% npm/2.4.0.tgz::darwin-arm64/quick-lint-js | 655376 | 654528 | 0% npm/2.4.0.tgz::darwin-x64/quick-lint-js | 1244384 | 1221152 | -2% npm/2.4.0.tgz::linux-arm/quick-lint-js | 496944 | 492848 | -1% npm/2.4.0.tgz::linux-arm64/quick-lint-js | 771000 | 750520 | -3% npm/2.4.0.tgz::linux-x64/quick-lint-js | 831528 | 823336 | -1% npm/2.4.0.tgz::win32-arm64/quick-lint-js.exe | 1017856 | 1012224 | -1% npm/2.4.0.tgz::win32-ia32/quick-lint-js.exe | 1165838 | 1163790 | 0% npm/2.4.0.tgz::win32-x64/quick-lint-js.exe | 1185280 | 1177088 | -1% vscode/2.4.0.vsix::darwin-arm64.node | 507144 | 539320 | 6% vscode/2.4.0.vsix::darwin-x64.node | 769008 | 805024 | 5% vscode/2.4.0.vsix::linux-arm.node | 436068 | 431972 | -1% vscode/2.4.0.vsix::linux-arm64.node | 685840 | 669456 | -2% vscode/2.4.0.vsix::linux-x64.node | 747760 | 735472 | -2% vscode/2.4.0.vsix::win32-arm.node | 514560 | 551936 | 7% vscode/2.4.0.vsix::win32-arm64.node | 642560 | 677376 | 5% vscode/2.4.0.vsix::win32-ia32.node | 921102 | 919054 | 0% vscode/2.4.0.vsix::win32-x64.node | 928768 | 923136 | -1% website/website.zip::vscode.wasm | 290283 | 328057 | 13% The size of quick-lint-js.wasm and the Visual Studio Code .node files increased because the translation table is now included in the binary. Previously the translation table was dead-stripped because localization is disabled for WASM and for VS Code. We want the localization table in the .wasm file [2] and VS Code extension [3] anyway, so the size increase is tolerable. [1] #687 [2] #433 [3] #529
1 parent dcf063e commit 595586a

19 files changed

+1992
-577
lines changed

benchmark/benchmark-translation.cpp

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <benchmark/benchmark.h>
55
#include <quick-lint-js/assert.h>
6+
#include <quick-lint-js/string-view.h>
67
#include <quick-lint-js/translation.h>
78

89
namespace quick_lint_js {
@@ -12,56 +13,73 @@ void benchmark_translate_from_source_code(benchmark::State &state) {
1213
messages.use_messages_from_source_code();
1314
for (auto _ : state) {
1415
::benchmark::DoNotOptimize(messages.translate(
15-
"variable assigned before its declaration"_translatable));
16+
QLJS_TRANSLATABLE("variable assigned before its declaration")));
1617
::benchmark::DoNotOptimize(
17-
messages.translate("variable declared here"_translatable));
18-
::benchmark::DoNotOptimize(messages.translate(
19-
"~~~ invalid string, do not use outside benchmark ~~~"_translatable));
18+
messages.translate(QLJS_TRANSLATABLE("variable declared here")));
19+
::benchmark::DoNotOptimize(messages.translate(QLJS_TRANSLATABLE(
20+
"~~~ invalid string, do not use outside benchmark ~~~")));
2021
}
2122
}
2223
BENCHMARK(benchmark_translate_from_source_code);
2324

25+
struct translatable_message_with_original {
26+
translatable_message translatable;
27+
const char *original;
28+
};
29+
2430
void benchmark_translate_from_translation_hit(benchmark::State &state) {
25-
static constexpr translatable_message messages_to_translate[] = {
26-
"variable assigned before its declaration"_translatable,
27-
"variable declared here"_translatable,
28-
};
31+
static constexpr translatable_message_with_original messages_to_translate[] =
32+
{
33+
{QLJS_TRANSLATABLE("variable assigned before its declaration"),
34+
"variable assigned before its declaration"},
35+
{QLJS_TRANSLATABLE("variable declared here"),
36+
"variable declared here"},
37+
};
2938

3039
translatable_messages messages;
3140
bool have_translation = messages.use_messages_from_locale("en@loud");
3241
QLJS_ALWAYS_ASSERT(have_translation);
33-
for (const translatable_message &message : messages_to_translate) {
42+
for (const translatable_message_with_original &message :
43+
messages_to_translate) {
3444
// Messages should be translated.
35-
QLJS_ALWAYS_ASSERT(
36-
std::strcmp(messages.translate(message), message.c_str()) != 0);
45+
QLJS_ALWAYS_ASSERT(std::strcmp(messages.translate(message.translatable),
46+
message.original) != 0);
3747
}
3848

3949
for (auto _ : state) {
40-
for (const translatable_message &message : messages_to_translate) {
41-
::benchmark::DoNotOptimize(messages.translate(message));
50+
for (const translatable_message_with_original &message :
51+
messages_to_translate) {
52+
::benchmark::DoNotOptimize(messages.translate(message.translatable));
4253
}
4354
}
4455
}
4556
BENCHMARK(benchmark_translate_from_translation_hit);
4657

4758
void benchmark_translate_from_translation_miss(benchmark::State &state) {
48-
static constexpr translatable_message messages_to_translate[] = {
49-
"~~~ invalid string, do not use outside benchmark ~~~"_translatable,
50-
"another invalid string, do not use outside benchmark"_translatable,
51-
};
59+
static constexpr translatable_message_with_original messages_to_translate[] =
60+
{
61+
{QLJS_TRANSLATABLE(
62+
"~~~ invalid string, do not use outside benchmark ~~~"),
63+
"~~~ invalid string, do not use outside benchmark ~~~"},
64+
{QLJS_TRANSLATABLE(
65+
"another invalid string, do not use outside benchmark"),
66+
"another invalid string, do not use outside benchmark"},
67+
};
5268

5369
translatable_messages messages;
5470
bool have_translation = messages.use_messages_from_locale("en@loud");
5571
QLJS_ALWAYS_ASSERT(have_translation);
56-
for (const translatable_message &message : messages_to_translate) {
72+
for (const translatable_message_with_original &message :
73+
messages_to_translate) {
5774
// Messages should not be translated.
58-
QLJS_ALWAYS_ASSERT(
59-
std::strcmp(messages.translate(message), message.c_str()) == 0);
75+
QLJS_ALWAYS_ASSERT(std::strcmp(messages.translate(message.translatable),
76+
message.original) == 0);
6077
}
6178

6279
for (auto _ : state) {
63-
for (const translatable_message &message : messages_to_translate) {
64-
::benchmark::DoNotOptimize(messages.translate(message));
80+
for (const translatable_message_with_original &message :
81+
messages_to_translate) {
82+
::benchmark::DoNotOptimize(messages.translate(message.translatable));
6583
}
6684
}
6785
}
@@ -87,7 +105,9 @@ BENCHMARK_CAPTURE(benchmark_load_translations, posix, "POSIX");
87105

88106
void benchmark_load_translations_and_find_hit(benchmark::State &state) {
89107
static constexpr translatable_message message_to_translate =
90-
"variable assigned before its declaration"_translatable;
108+
QLJS_TRANSLATABLE("variable assigned before its declaration");
109+
static constexpr const char *untranslated_message =
110+
"variable assigned before its declaration";
91111

92112
const char *locale = "en@loud";
93113
{
@@ -96,7 +116,7 @@ void benchmark_load_translations_and_find_hit(benchmark::State &state) {
96116
bool have_translation = messages.use_messages_from_locale(locale);
97117
QLJS_ALWAYS_ASSERT(have_translation);
98118
QLJS_ALWAYS_ASSERT(std::strcmp(messages.translate(message_to_translate),
99-
message_to_translate.c_str()) != 0);
119+
untranslated_message) != 0);
100120
}
101121

102122
for (auto _ : state) {
@@ -110,7 +130,9 @@ BENCHMARK(benchmark_load_translations_and_find_hit);
110130

111131
void benchmark_load_translations_and_find_miss(benchmark::State &state) {
112132
static constexpr translatable_message message_to_translate =
113-
"~~~ invalid string, do not use outside benchmark ~~~"_translatable;
133+
QLJS_TRANSLATABLE("~~~ invalid string, do not use outside benchmark ~~~");
134+
static constexpr const char *untranslated_message =
135+
"~~~ invalid string, do not use outside benchmark ~~~";
114136

115137
const char *locale = "en@loud";
116138
{
@@ -119,7 +141,7 @@ void benchmark_load_translations_and_find_miss(benchmark::State &state) {
119141
bool have_translation = messages.use_messages_from_locale(locale);
120142
QLJS_ALWAYS_ASSERT(have_translation);
121143
QLJS_ALWAYS_ASSERT(std::strcmp(messages.translate(message_to_translate),
122-
message_to_translate.c_str()) == 0);
144+
untranslated_message) == 0);
123145
}
124146

125147
for (auto _ : state) {

po/de.po

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ msgid ""
2020
msgstr ""
2121
"Project-Id-Version: PACKAGE VERSION\n"
2222
"Report-Msgid-Bugs-To: [email protected]\n"
23-
"POT-Creation-Date: 2022-04-15 21:50-0700\n"
23+
"POT-Creation-Date: 2022-04-16 00:28-0700\n"
2424
"PO-Revision-Date: 2021-11-18 03:48+0100\n"
2525
"Last-Translator: Nico Sonack <[email protected]>\n"
2626
"Language-Team: German\n"
@@ -30,6 +30,22 @@ msgstr ""
3030
"Content-Transfer-Encoding: 8bit\n"
3131
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
3232

33+
#: benchmark/benchmark-translation.cpp src/quick-lint-js/error.h
34+
msgid "variable assigned before its declaration"
35+
msgstr "Zuweisung an Variable vor Deklaration"
36+
37+
#: benchmark/benchmark-translation.cpp src/quick-lint-js/error.h
38+
msgid "variable declared here"
39+
msgstr "Variablendeklaration ist hier"
40+
41+
#: benchmark/benchmark-translation.cpp
42+
msgid "~~~ invalid string, do not use outside benchmark ~~~"
43+
msgstr ""
44+
45+
#: benchmark/benchmark-translation.cpp
46+
msgid "another invalid string, do not use outside benchmark"
47+
msgstr ""
48+
3349
#: src/diagnostic-formatter.cpp
3450
msgid "'do-while' loop"
3551
msgstr ""
@@ -82,14 +98,6 @@ msgstr ""
8298
msgid "children end here"
8399
msgstr ""
84100

85-
#: src/quick-lint-js/error.h
86-
msgid "variable assigned before its declaration"
87-
msgstr "Zuweisung an Variable vor Deklaration"
88-
89-
#: src/quick-lint-js/error.h
90-
msgid "variable declared here"
91-
msgstr "Variablendeklaration ist hier"
92-
93101
#: src/quick-lint-js/error.h
94102
msgid "'=' changes variables; to compare, use '===' instead"
95103
msgstr ""
@@ -1068,5 +1076,46 @@ msgstr ""
10681076
msgid "continue can only be used inside of a loop"
10691077
msgstr "continue ist nur innerhalb von Schleifen gültig"
10701078

1079+
#: test/test-diagnostic-formatter.cpp
1080+
#: test/test-vim-qflist-json-error-reporter.cpp
1081+
msgid "something happened"
1082+
msgstr ""
1083+
1084+
#: test/test-diagnostic-formatter.cpp
1085+
msgid "see here"
1086+
msgstr ""
1087+
1088+
#: test/test-diagnostic-formatter.cpp
1089+
msgid "this {0} looks fishy"
1090+
msgstr ""
1091+
1092+
#: test/test-diagnostic-formatter.cpp
1093+
msgid "this {1} looks fishy"
1094+
msgstr ""
1095+
1096+
#: test/test-diagnostic-formatter.cpp
1097+
msgid "free {1} and {0} {1} {2}"
1098+
msgstr ""
1099+
1100+
#: test/test-diagnostic-formatter.cpp
1101+
msgid "what is this '{1}' nonsense?"
1102+
msgstr ""
1103+
1104+
#: test/test-diagnostic-formatter.cpp
1105+
msgid "a {{0} b }} c"
1106+
msgstr ""
1107+
1108+
#: test/test-diagnostic-formatter.cpp
1109+
msgid "expected {1:headlinese}"
1110+
msgstr ""
1111+
1112+
#: test/test-diagnostic-formatter.cpp
1113+
msgid "expected {1:singular}"
1114+
msgstr ""
1115+
1116+
#: test/test-vim-qflist-json-error-reporter.cpp
1117+
msgid "here"
1118+
msgstr ""
1119+
10711120
#~ msgid "invalid binding in let statement"
10721121
#~ msgstr "Ungültiges Binding in let-Statement"

po/[email protected]

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ msgid ""
2020
msgstr ""
2121
"Project-Id-Version: PACKAGE VERSION\n"
2222
"Report-Msgid-Bugs-To: [email protected]\n"
23-
"POT-Creation-Date: 2022-04-15 21:50-0700\n"
23+
"POT-Creation-Date: 2022-04-16 00:28-0700\n"
2424
"PO-Revision-Date: 2021-11-17 17:05+0100\n"
2525
"Last-Translator: Matthew \"strager\" Glazar <[email protected]>\n"
2626
"Language-Team: English\n"
@@ -30,6 +30,22 @@ msgstr ""
3030
"Content-Transfer-Encoding: 8bit\n"
3131
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
3232

33+
#: benchmark/benchmark-translation.cpp src/quick-lint-js/error.h
34+
msgid "variable assigned before its declaration"
35+
msgstr "VARIABLE USED BEFORE DECLARATION: {0}"
36+
37+
#: benchmark/benchmark-translation.cpp src/quick-lint-js/error.h
38+
msgid "variable declared here"
39+
msgstr "VARIABLE DECLARED HERE"
40+
41+
#: benchmark/benchmark-translation.cpp
42+
msgid "~~~ invalid string, do not use outside benchmark ~~~"
43+
msgstr ""
44+
45+
#: benchmark/benchmark-translation.cpp
46+
msgid "another invalid string, do not use outside benchmark"
47+
msgstr ""
48+
3349
#: src/diagnostic-formatter.cpp
3450
msgid "'do-while' loop"
3551
msgstr ""
@@ -82,14 +98,6 @@ msgstr ""
8298
msgid "children end here"
8399
msgstr ""
84100

85-
#: src/quick-lint-js/error.h
86-
msgid "variable assigned before its declaration"
87-
msgstr "VARIABLE USED BEFORE DECLARATION: {0}"
88-
89-
#: src/quick-lint-js/error.h
90-
msgid "variable declared here"
91-
msgstr "VARIABLE DECLARED HERE"
92-
93101
#: src/quick-lint-js/error.h
94102
msgid "'=' changes variables; to compare, use '===' instead"
95103
msgstr ""
@@ -1047,5 +1055,46 @@ msgstr "BREAK CAN ONLY BE USED INSIDE OF A LOOP OR SWITCH"
10471055
msgid "continue can only be used inside of a loop"
10481056
msgstr "CONTINUE CAN ONLY BE USED INSIDE OF A LOOP"
10491057

1058+
#: test/test-diagnostic-formatter.cpp
1059+
#: test/test-vim-qflist-json-error-reporter.cpp
1060+
msgid "something happened"
1061+
msgstr ""
1062+
1063+
#: test/test-diagnostic-formatter.cpp
1064+
msgid "see here"
1065+
msgstr ""
1066+
1067+
#: test/test-diagnostic-formatter.cpp
1068+
msgid "this {0} looks fishy"
1069+
msgstr ""
1070+
1071+
#: test/test-diagnostic-formatter.cpp
1072+
msgid "this {1} looks fishy"
1073+
msgstr ""
1074+
1075+
#: test/test-diagnostic-formatter.cpp
1076+
msgid "free {1} and {0} {1} {2}"
1077+
msgstr ""
1078+
1079+
#: test/test-diagnostic-formatter.cpp
1080+
msgid "what is this '{1}' nonsense?"
1081+
msgstr ""
1082+
1083+
#: test/test-diagnostic-formatter.cpp
1084+
msgid "a {{0} b }} c"
1085+
msgstr ""
1086+
1087+
#: test/test-diagnostic-formatter.cpp
1088+
msgid "expected {1:headlinese}"
1089+
msgstr ""
1090+
1091+
#: test/test-diagnostic-formatter.cpp
1092+
msgid "expected {1:singular}"
1093+
msgstr ""
1094+
1095+
#: test/test-vim-qflist-json-error-reporter.cpp
1096+
msgid "here"
1097+
msgstr ""
1098+
10501099
#~ msgid "invalid binding in let statement"
10511100
#~ msgstr "INVALID BINDING IN LET STATEMENT"

0 commit comments

Comments
 (0)