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 }