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