glogging
Loading...
Searching...
No Matches
gmessageCatalog.h
Go to the documentation of this file.
1#pragma once
2
3#include <array>
4#include <filesystem>
5#include <sstream>
6#include <stdexcept>
7#include <utility>
8#include <map>
9#include <memory>
10#include <string>
11#include <string_view>
12#include <vector>
13
14class GOptions;
15
22class GMessageCatalog {
23public:
28 [[nodiscard]] static std::shared_ptr<const GMessageCatalog> load(const GOptions* options = nullptr);
29
31 [[nodiscard]] static std::vector<std::filesystem::path> searchDirectories(const GOptions* options = nullptr);
32
34 [[nodiscard]] std::string_view translate(std::string_view english) const;
35
41 template <typename... Args>
42 [[nodiscard]] std::string format(std::string_view message, Args&&... args) const {
43 static_assert(sizeof...(Args) <= 99, "Message templates support at most 99 parameters");
44 message = translate(message);
45 const std::array<std::string, sizeof...(Args)> parameters{
46 parameter_string(std::forward<Args>(args))...
47 };
48 std::array<bool, sizeof...(Args)> used{};
49 std::string result;
50 result.reserve(message.size());
51
52 for (std::size_t position = 0; position < message.size();) {
53 const auto next = position + 1;
54 if (message[position] != '%' || next == message.size()) {
55 result += message[position++];
56 continue;
57 }
58 if (message[next] == '%') {
59 result += '%';
60 position += 2;
61 continue;
62 }
63 if (message[next] < '0' || message[next] > '9') {
64 result += message[position++];
65 continue;
66 }
67 if (message[next] == '0') {
68 throw std::invalid_argument("Message placeholders must start with %1 through %99");
69 }
70
71 std::size_t index = 0;
72 position = next;
73 while (position < message.size() && message[position] >= '0' && message[position] <= '9') {
74 index = index * 10 + static_cast<std::size_t>(message[position++] - '0');
75 if (index > 99) throw std::invalid_argument("Message placeholder exceeds %99");
76 }
77 if (index > parameters.size()) {
78 throw std::invalid_argument("Message placeholder has no corresponding parameter");
79 }
80 result += parameters[index - 1];
81 used[index - 1] = true;
82 }
83 for (bool referenced : used) {
84 if (!referenced) throw std::invalid_argument("Message template leaves a parameter unused");
85 }
86 return result;
87 }
88
90 [[nodiscard]] const std::string& language() const { return language_; }
91
92private:
93 template <typename T>
94 [[nodiscard]] static std::string parameter_string(T&& value) {
95 std::ostringstream stream;
96 stream << std::forward<T>(value);
97 return stream.str();
98 }
99
100 explicit GMessageCatalog(std::string language, const std::vector<std::filesystem::path>& directories);
101 std::string language_;
102 std::map<std::string, std::string, std::less<>> messages_;
103};
std::string_view translate(std::string_view english) const
Returns a translated template, or the original view when no translation is available.
const std::string & language() const
Normalized requested language tag; a missing catalog still falls back to English per message.
static std::shared_ptr< const GMessageCatalog > load(const GOptions *options=nullptr)
Resolve language and catalog paths from options/environment and reuse the cached catalog.
std::string format(std::string_view message, Args &&... args) const
Translate and format a complete template without logging headers or verbosity filtering....
static std::vector< std::filesystem::path > searchDirectories(const GOptions *options=nullptr)
Catalog search paths shared by logging and GUI translation startup.