1 /*
2     gettext .mo support
3 */
4 module i18n.mo;
5 import i18n.tr;
6 import std.format;
7 import std.exception;
8 import std.string : fromStringz;
9 import std.bitmanip : swapEndian;
10 
11 private:
12 struct MOEntry {
13 align(1):
14     uint length;
15     uint offset;
16 }
17 
18 struct MOHeader {
19 align(1):
20     uint magic;
21     uint revision;
22     uint count;
23     uint sourceOffset;
24     uint targetOffset;
25     uint hashSize;
26     void* hashes;
27 }
28 
29 struct MOFile {
30     MOHeader header;
31     MOEntry* sources;
32     MOEntry* targets;
33 }
34 
35 enum MAGIC_SWAB = 0xde120495;
36 enum MAGIC = 0x950412de;
37 
38 pragma(inline, true)
39 uint endian(uint x) { return ((mo.header.magic == MAGIC_SWAB) ? swapEndian(x) : (x)); }
40 
41 bool mo_init = false;
42 ubyte[] mo_data;
43 MOFile mo;
44 
45 public:
46 
47 void i18nMOLoad(ubyte[] data) {
48     mo_init = true;
49     if (!data) {
50         mo_data = null;
51         return;
52     }
53 
54     (cast(void*)&mo.header)[0..MOHeader.sizeof] = data[0..MOHeader.sizeof];
55     if (mo.header.magic == MAGIC_SWAB) {
56         mo.header.revision = swapEndian(mo.header.revision);
57         mo.header.count = swapEndian(mo.header.count);
58         mo.header.sourceOffset = swapEndian(mo.header.sourceOffset);
59         mo.header.targetOffset = swapEndian(mo.header.targetOffset);
60         mo.header.hashSize = swapEndian(mo.header.hashSize);
61         mo.header.hashes = cast(void*)swapEndian(cast(size_t)mo.header.magic);
62     }
63 
64     enforce(mo.header.magic == MAGIC, "Bad mo file magic 0x%08x".format(mo.header.magic));
65     enforce(mo.header.revision == 0, "Bad mo file revision 0x%08x".format(mo.header.revision));
66 
67     mo_data = data;
68     mo.sources = cast(MOEntry*)(mo_data.ptr+mo.header.sourceOffset);
69     mo.targets = cast(MOEntry*)(mo_data.ptr+mo.header.targetOffset);
70 }
71 
72 string i18nMOStr(string str) {
73 
74     // No data was found
75     if (!mo_init || !mo_data) return str;
76 
77     foreach(i; 0..mo.header.count) {
78         if (str == cast(string)mo_data[mo.sources[i].offset..endian(mo.sources[i].offset)+endian(mo.sources[i].length)]) {
79             
80             // Empty translation? return base string.
81             if (mo.targets[i].length == 0) return str;
82 
83             // Return the correct translation (pluralization 0)
84             return cast(string)mo_data[mo.targets[i].offset..endian(mo.targets[i].offset)+endian(mo.targets[i].length)];
85         }
86     }
87 
88     return str;
89 }
90 
91 const(char)* i18nMOStrC(string str) {
92     import std.string : toStringz;
93 
94     // No data was found
95     if (!mo_init || !mo_data) return str.toStringz;
96 
97     foreach(i; 0..mo.header.count) {
98         if (str == cast(string)mo_data[mo.sources[i].offset..endian(mo.sources[i].offset)+endian(mo.sources[i].length)]) {
99             
100             // Empty translation? return base string.
101             if (mo.targets[i].length == 0) return str.toStringz;
102 
103             // Return the correct translation (pluralization 0)
104             return cast(const(char)*)&mo_data[mo.targets[i].offset];
105         }
106     }
107 
108     return str.toStringz;
109 }
110 
111 TREntry[string] i18nMOGenStrings() {
112     import std.string : toStringz;
113     TREntry[string] entries;
114     
115     foreach(i; 0..mo.header.count) {
116 
117         // Skip untranslated entries
118         if (mo.targets[i].length == 0) continue;
119 
120         // Add translation to translation table
121         // TODO: Add pluralizations
122         string source = cast(string)mo_data[mo.sources[i].offset..endian(mo.sources[i].offset)+endian(mo.sources[i].length)];
123         string target0 = cast(string)mo_data[mo.targets[i].offset..endian(mo.targets[i].offset)+endian(mo.targets[i].length)];
124         entries[source] = TREntry(source, [target0], [target0.toStringz]);
125     }
126 
127     return entries;
128 }