Skip to content

Commit c43c637

Browse files
authored
Merge pull request #679 from kiwix/kiwix-serve-i18n
2 parents e22e073 + 927c125 commit c43c637

21 files changed

+772
-68
lines changed

debian/libkiwix-dev.manpages

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
usr/share/man/man1/kiwix-compile-resources.1*
2+
usr/share/man/man1/kiwix-compile-i18n.1*

scripts/kiwix-compile-i18n

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#!/usr/bin/env python3
2+
3+
'''
4+
Copyright 2022 Veloman Yunkan <[email protected]>
5+
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation; either version 3 of the License, or any
9+
later version.
10+
11+
This program is distributed in the hope that it will be useful, but
12+
WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program; if not, write to the Free Software
18+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19+
02110-1301, USA.
20+
'''
21+
22+
import argparse
23+
import os.path
24+
import re
25+
import json
26+
27+
def to_identifier(name):
28+
ident = re.sub(r'[^0-9a-zA-Z]', '_', name)
29+
if ident[0].isnumeric():
30+
return "_"+ident
31+
return ident
32+
33+
def lang_code(filename):
34+
filename = os.path.basename(filename)
35+
lang = to_identifier(os.path.splitext(filename)[0])
36+
print(filename, '->', lang)
37+
return lang
38+
39+
from string import Template
40+
41+
def expand_cxx_template(t, **kwargs):
42+
return Template(t).substitute(**kwargs)
43+
44+
def cxx_string_literal(s):
45+
# Taking advantage of the fact the JSON string escape rules match
46+
# those of C++
47+
return 'u8' + json.dumps(s)
48+
49+
string_table_cxx_template = '''
50+
const I18nString $TABLE_NAME[] = {
51+
$TABLE_ENTRIES
52+
};
53+
'''
54+
55+
lang_table_entry_cxx_template = '''
56+
{
57+
$LANG_STRING_LITERAL,
58+
ARRAY_ELEMENT_COUNT($STRING_TABLE_NAME),
59+
$STRING_TABLE_NAME
60+
}'''
61+
62+
cxxfile_template = '''// This file is automatically generated. Do not modify it.
63+
64+
#include "server/i18n.h"
65+
66+
namespace kiwix {
67+
namespace i18n {
68+
69+
namespace
70+
{
71+
72+
$STRING_DATA
73+
74+
} // unnamed namespace
75+
76+
#define ARRAY_ELEMENT_COUNT(a) (sizeof(a)/sizeof(a[0]))
77+
78+
extern const I18nStringTable stringTables[] = {
79+
$LANG_TABLE
80+
};
81+
82+
extern const size_t langCount = $LANG_COUNT;
83+
84+
} // namespace i18n
85+
} // namespace kiwix
86+
'''
87+
88+
class Resource:
89+
def __init__(self, base_dirs, filename):
90+
filename = filename.strip()
91+
self.filename = filename
92+
self.lang_code = lang_code(filename)
93+
found = False
94+
for base_dir in base_dirs:
95+
try:
96+
with open(os.path.join(base_dir, filename), 'r') as f:
97+
self.data = f.read()
98+
found = True
99+
break
100+
except FileNotFoundError:
101+
continue
102+
if not found:
103+
raise Exception("Impossible to find {}".format(filename))
104+
105+
106+
def get_string_table_name(self):
107+
return "string_table_for_" + self.lang_code
108+
109+
def get_string_table(self):
110+
table_entries = ",\n ".join(self.get_string_table_entries())
111+
return expand_cxx_template(string_table_cxx_template,
112+
TABLE_NAME=self.get_string_table_name(),
113+
TABLE_ENTRIES=table_entries)
114+
115+
def get_string_table_entries(self):
116+
d = json.loads(self.data)
117+
for k in sorted(d.keys()):
118+
if k != "@metadata":
119+
key_string = cxx_string_literal(k)
120+
value_string = cxx_string_literal(d[k])
121+
yield '{ ' + key_string + ', ' + value_string + ' }'
122+
123+
def get_lang_table_entry(self):
124+
return expand_cxx_template(lang_table_entry_cxx_template,
125+
LANG_STRING_LITERAL=cxx_string_literal(self.lang_code),
126+
STRING_TABLE_NAME=self.get_string_table_name())
127+
128+
129+
130+
def gen_c_file(resources):
131+
string_data = []
132+
lang_table = []
133+
for r in resources:
134+
string_data.append(r.get_string_table())
135+
lang_table.append(r.get_lang_table_entry())
136+
137+
return expand_cxx_template(cxxfile_template,
138+
STRING_DATA="\n".join(string_data),
139+
LANG_TABLE=",\n ".join(lang_table),
140+
LANG_COUNT=len(resources)
141+
)
142+
143+
144+
145+
if __name__ == "__main__":
146+
parser = argparse.ArgumentParser()
147+
parser.add_argument('--cxxfile',
148+
required=True,
149+
help='The Cpp file name to generate')
150+
parser.add_argument('i18n_resource_file',
151+
help='The list of resources to compile.')
152+
args = parser.parse_args()
153+
154+
base_dir = os.path.dirname(os.path.realpath(args.i18n_resource_file))
155+
with open(args.i18n_resource_file, 'r') as f:
156+
resources = [Resource([base_dir], filename)
157+
for filename in f.readlines()]
158+
159+
with open(args.cxxfile, 'w') as f:
160+
f.write(gen_c_file(resources))
161+

scripts/kiwix-compile-i18n.1

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.TH KIWIX-COMPILE-I18N "1" "January 2022" "Kiwix" "User Commands"
2+
.SH NAME
3+
kiwix-compile-i18n \- helper to compile Kiwix i18n (internationalization) data
4+
.SH SYNOPSIS
5+
\fBkiwix\-compile\-i18n\fR [\-h] \-\-cxxfile CXXFILE i18n_resource_file\fR
6+
.SH DESCRIPTION
7+
.TP
8+
i18n_resource_file
9+
The list of i18n resources to compile.
10+
.TP
11+
\fB\-h\fR, \fB\-\-help\fR
12+
show a help message and exit
13+
.TP
14+
\fB\-\-cxxfile\fR CXXFILE
15+
The Cpp file name to generate
16+
.TP
17+
.SH AUTHOR
18+
Veloman Yunkan <[email protected]>

scripts/kiwix-compile-resources

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class Resource:
102102

103103

104104

105-
master_c_template = """//This file is automaically generated. Do not modify it.
105+
master_c_template = """//This file is automatically generated. Do not modify it.
106106
107107
#include <stdlib.h>
108108
#include <fstream>

scripts/meson.build

+6
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,9 @@ res_compiler = find_program('kiwix-compile-resources')
44
install_data(res_compiler.path(), install_dir:get_option('bindir'))
55

66
install_man('kiwix-compile-resources.1')
7+
8+
i18n_compiler = find_program('kiwix-compile-i18n')
9+
10+
install_data(i18n_compiler.path(), install_dir:get_option('bindir'))
11+
12+
install_man('kiwix-compile-i18n.1')

src/meson.build

+2
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ kiwix_sources = [
2828
'server/response.cpp',
2929
'server/internalServer.cpp',
3030
'server/internalServer_catalog_v2.cpp',
31+
'server/i18n.cpp',
3132
'opds_catalog.cpp',
3233
'version.cpp'
3334
]
3435
kiwix_sources += lib_resources
36+
kiwix_sources += i18n_resources
3537

3638
if host_machine.system() == 'windows'
3739
kiwix_sources += 'subprocess_windows.cpp'

src/server/i18n.cpp

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright 2022 Veloman Yunkan <[email protected]>
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 3 of the License, or
7+
* any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17+
* MA 02110-1301, USA.
18+
*/
19+
20+
#include "i18n.h"
21+
22+
#include "tools/otherTools.h"
23+
24+
#include <algorithm>
25+
#include <map>
26+
27+
namespace kiwix
28+
{
29+
30+
const char* I18nStringTable::get(const std::string& key) const
31+
{
32+
const I18nString* const begin = entries;
33+
const I18nString* const end = begin + entryCount;
34+
const I18nString* found = std::lower_bound(begin, end, key,
35+
[](const I18nString& a, const std::string& k) {
36+
return a.key < k;
37+
});
38+
return (found == end || found->key != key) ? nullptr : found->value;
39+
}
40+
41+
namespace i18n
42+
{
43+
// this data is generated by the i18n resource compiler
44+
extern const I18nStringTable stringTables[];
45+
extern const size_t langCount;
46+
}
47+
48+
namespace
49+
{
50+
51+
class I18nStringDB
52+
{
53+
public: // functions
54+
I18nStringDB() {
55+
for ( size_t i = 0; i < kiwix::i18n::langCount; ++i ) {
56+
const auto& t = kiwix::i18n::stringTables[i];
57+
lang2TableMap[t.lang] = &t;
58+
}
59+
enStrings = lang2TableMap.at("en");
60+
};
61+
62+
std::string get(const std::string& lang, const std::string& key) const {
63+
const char* s = getStringsFor(lang)->get(key);
64+
if ( s == nullptr ) {
65+
s = enStrings->get(key);
66+
if ( s == nullptr ) {
67+
throw std::runtime_error("Invalid message id");
68+
}
69+
}
70+
return s;
71+
}
72+
73+
private: // functions
74+
const I18nStringTable* getStringsFor(const std::string& lang) const {
75+
try {
76+
return lang2TableMap.at(lang);
77+
} catch(const std::out_of_range&) {
78+
return enStrings;
79+
}
80+
}
81+
82+
private: // data
83+
std::map<std::string, const I18nStringTable*> lang2TableMap;
84+
const I18nStringTable* enStrings;
85+
};
86+
87+
} // unnamed namespace
88+
89+
std::string getTranslatedString(const std::string& lang, const std::string& key)
90+
{
91+
static const I18nStringDB stringDb;
92+
93+
return stringDb.get(lang, key);
94+
}
95+
96+
namespace i18n
97+
{
98+
99+
std::string expandParameterizedString(const std::string& lang,
100+
const std::string& key,
101+
const Parameters& params)
102+
{
103+
const std::string tmpl = getTranslatedString(lang, key);
104+
return render_template(tmpl, params);
105+
}
106+
107+
} // namespace i18n
108+
109+
std::string ParameterizedMessage::getText(const std::string& lang) const
110+
{
111+
return i18n::expandParameterizedString(lang, msgId, params);
112+
}
113+
114+
} // namespace kiwix

0 commit comments

Comments
 (0)