1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
|
#ifndef StringTable_h_
#define StringTable_h_
//! @file
//! Declares the StringTable class.
#include <boost/unordered_map.hpp>
#include <string>
#include <set>
#include <mutex>
#include <memory>
//! Provides a key based translation string lookup with fallback.
//!
//! StringTable is a map to look up translations based on key representing a
//! translation entry. When a translation for a key is not found the
//! StringTable delegates the lookup to another fallback StringTable, which is
//! filled with entries from another language. When no fallback StringTable
//! contains the requested translation entry an error string is returned,
//! pointing out the missing translation entry.
//!
//! StringTables are loaded from text files which contain the translation
//! entries for a specific language. Those translation files are named like
//!
//! ```{.txt}
//! <LANG ISO 639-1>.txt
//! ```
//!
//! where <LANG ISO 639-1> is the two letter language identifier for a language
//! according to ISO 639-1 (e.g English=en, Russian=ru, German=de, ...).
//!
//! The format of those translation files consists of newline separated entries
//! where:
//!
//! * The first line is the native language name.
//! * Linse starting with a hash sign `#` are considered comments and are
//! ignored when loading the file.
//! * Empty lines are ignored when loading the file.
//! * The translation entries consist of an key followed by a newline, followed
//! by the translated text followed by a newline.
//! * An identifer must be unique within an translation file.
//! * Identifiers consist of the any latin character, digits or an underscore.
//! Spaces or other characters are not allowed.
//! * The translated text is a valid UTF-8 based string, which will either be
//! terminated by a newline, trimming of any whitespace at the begining or end
//! of the translated string or a multi-line string.
//! * A multi-line string starts and ends with three single quotes `'''`. As the
//! name implies, a multi-line string can span over multiple lines and any
//! whitespace inside the string will be preseved.
//!
//! A minimal example translation file for the english language `en.txt` should
//! look like:
//!
//! ```{.txt}
//! English
//! # This line is a comment, the line above is the native language name.
//!
//! # The two lines below are an translation entry consisting of an identifer
//! # and a single line of translated text.
//! A_SINGLE_LINE
//! This is the translated text associated with A_SINGLE_LINE
//!
//! # The line below is a translation entry with multiple lines of translated
//! # text.
//! A_MULTI_LINE
//! '''This text
//! spans over
//! multiple lines.
//! The whitespace before this line is preseved, same goes for the newlines
//! after this line up to the triple single quotes.
//!
//!
//! '''
//!
//! # This translation entry doesn't preserve whitespace
//! SINGLE_LINE_WS_TRIM
//! This text is intended in the translation file, but the whitespace will be trimmed.
//!
//! # This translation entry preseves whitespace
//! SINGLE_LINE_WS_PRESERVE
//! ''' This text will keep its leading and trailing whitespace '''
//! ```
//!
//! StringTables implement multiple string substitution mechanism.
//!
//! The first substitution mechanism replaces references to a translation entry
//! with the text of another translation entry. The syntax for a reference
//! consist of two opening square brackets `[[`, the referenced key and two
//! closing brackets `]]`. For example:
//!
//! ```{.txt}
//! REFERENCE
//! I am the replaced text
//!
//! TRANSLATION_ENTRY
//! This translation embeds another translation right here > [[REFERENCE]]
//! ```
//!
//! would return
//!
//! ```{.txt}
//! This translation embeds another translaion right here > I am the replaced text
//! ```
//!
//! The second substitution mechanism replaces typed references with type links
//! and the translation text. The syntax for a typed reference consists of two
//! opening square brackets `[[`, a type key, a space, the referenced key and
//! two closing brackets `]]``. For example:
//!
//! ```{.txt}
//! REFERENCE
//! I am referenced
//!
//! TRANSLATION_ENTRY
//! This translation links to [[foo_type REFERENCE]]
//! ```
//!
//! would return
//!
//! ```{.txt}
//! This translation links to <foo_type REFERENCE>I am referenced</foo_type>
//! ```
class StringTable {
public:
//! Create an empty StringTable
StringTable() = default;
//! Create a StringTable from the given @p filename.
//!
//! @param filename
//! The file name of the translation file to load.
//! @param fallback
//! A StringTable that should be used look up unknown translation
//! entries.
explicit StringTable(std::string filename, std::shared_ptr<const StringTable> fallback = nullptr);
~StringTable() = default;
//! Returns if a translation for @p key exists. Caller should have shared (read)
//! access to this stringtable before calling this function.
//!
//! @param key
//! The identifying key of a translation entry.
//!
//! @return
//! True iff a translation with that key exists, false otherwise.
[[nodiscard]] bool StringExists(const std::string& key) const;
[[nodiscard]] bool StringExists(const std::string_view key) const;
[[nodiscard]] bool StringExists(const char* key) const;
//! Returns if a translation for @p key exists and what that translation is, if it exists.
//! Caller should have shared (read) access to this stringtable before calling this function.
//!
//! @param key
//! The identifying key of a translation entry.
//!
//! @return
//! pair containing true iff a translation with that key exists, false otherwise, and
//! reference to the translation or to an emptry string if no translation exists
[[nodiscard]] std::pair<bool, const std::string&> CheckGet(const std::string& key) const;
[[nodiscard]] std::pair<bool, const std::string&> CheckGet(const std::string_view key) const;
[[nodiscard]] std::pair<bool, const std::string&> CheckGet(const char* key) const;
//! Returns the native language name of this StringTable.
[[nodiscard]] const std::string& Language() const noexcept { return m_language; }
//! Returns the translation file name this StringTable was loaded from.
[[nodiscard]] const std::string& Filename() const noexcept { return m_filename; }
[[nodiscard]] const auto& AllStrings() const noexcept { return m_strings; }
//! Adds the a @p key and @p value pair to this StringTable, and returns a reference
//! to the newly-added string. If the key already exists, it is overwritten.
//! Caller should have exclusive (write) access to this stringtable before calling
//! this function.
//!
//! @param key
//! The identifying key of a translation entry.
//! @param value
//! The value to be stored with index key
//!
//! @return
//! A string for @p key containing value
const std::string& Add(std::string key, std::string value);
private:
//! Loads this StringTable from #m_filename
//!
//! @param fallback
//! A StringTable that should be used look up unknown translation
//! entries.
void Load(std::shared_ptr<const StringTable> fallback = nullptr);
//! The filename this StringTable was loaded from.
std::string m_filename;
//! The native language name of the StringTable translations.
std::string m_language;
struct hasher {
using is_transparent = void;
size_t operator()(const auto& key) const
{ return boost::hash_range(key.begin(), key.end()); }
size_t operator()(const char* key) const {
const std::string_view sv{key};
return boost::hash_range(sv.begin(), sv.end());
}
};
struct equalizer {
using is_transparent = void;
bool operator()(const auto& lhs, const auto& rhs) const noexcept
{ return lhs.compare(rhs) == 0; }
bool operator()(const char* lhs, const auto& rhs) const noexcept
{ return rhs.compare(lhs) == 0; }
};
//! Mapping of translation entry keys to translated strings.
boost::unordered_map<std::string, std::string, hasher, equalizer> m_strings;
//! True if the StringTable was completely loaded and all references
//! were successfully resolved.
bool m_initialized = false;
};
#endif
|