7#include "po_translations.hpp"
8#include "../i18n/i18n.hpp"
10#include "../parser/parser.hpp"
11#include "../macros.hpp"
17hi_export_module(hikogui.l10n.po_parser);
19hi_export
namespace hi {
inline namespace v1 {
23template<std::input_iterator It, std::sentinel_for<It> ItEnd>
26 hi_assert(it != last);
29 if ((*it == token::id)) {
33 std::format(
"{}: Expecting a keyword at start of each line, got {}",
token_location(it, last, path), *it));
40 if (it != last and *it == token::integer) {
41 index =
static_cast<size_t>(*it++);
43 throw parse_error(std::format(
44 "{}: Expecting an integer literal as an index for {}, got {}",
token_location(it, last, path), name, *it));
47 if (it != last and *it ==
']') {
50 throw parse_error(std::format(
51 "{}: The index on {} must terminate with a bracket ']', got {}",
token_location(it, last, path), name, *it));
56 if (it != last and (*it == token::sstr or *it == token::dstr)) {
60 std::format(
"{}: Expecting a string value after {}, got {}",
token_location(it, last, path), name, *it));
63 while (it != last and (*it == token::sstr or *it == token::dstr)) {
68 return {name, index, value};
71template<std::input_iterator It, std::sentinel_for<It> ItEnd>
72[[nodiscard]]
constexpr std::optional<po_translation> parse_po_translation(It& it, ItEnd last, std::string_view path)
77 if (r.msgstr.empty()) {
79 auto [name, index, value] = parse_po_line(it, last, path);
81 if (name ==
"msgctxt") {
84 }
else if (name ==
"msgid") {
87 }
else if (name ==
"msgid_plural") {
88 r.msgid_plural = value;
90 }
else if (name ==
"msgstr") {
91 if (index >= r.msgstr.size()) {
92 r.msgstr.resize(index + 1);
94 r.msgstr[index] = value;
98 std::format(
"{}: Line starts with unexpected keyword {}",
token_location(it, last, path), name));
101 }
else if ((*it == token::id) and (*it ==
"msgstr")) {
103 auto [name, index, value] = parse_po_line(it, last, path);
105 if (index >= r.msgstr.size()) {
106 r.msgstr.resize(index + 1);
108 r.msgstr[index] = value;
119constexpr void parse_po_header(po_translations& r, std::string_view header)
121 using namespace std::literals;
123 for (
auto const line :
std::views::split(header,
"\\n"sv)) {
129 auto split_line = make_vector<std::string_view>(std::views::split(line,
":"sv));
130 if (split_line.size() < 2) {
131 throw parse_error(std::format(
"Unknown header '{}'", std::string_view{line}));
134 auto const name = to_lower(strip(split_line.front()));
135 split_line.erase(split_line.begin());
136 auto const value = strip(join(split_line,
":"));
138 if (name ==
"language") {
139 r.language = language_tag{value};
146template<std::input_iterator It, std::sentinel_for<It> ItEnd>
147[[nodiscard]]
constexpr po_translations parse_po(It it, ItEnd last, std::string_view path)
151 auto token_it = lexer<lexer_config::sh_style()>.parse(it, last);
153 while (token_it != std::default_sentinel) {
154 if (
auto result = detail::parse_po_translation(token_it, std::default_sentinel, path)) {
155 if (not result->msgid.empty()) {
156 r.translations.push_back(*result);
158 }
else if (result->msgstr.size() == 1) {
160 detail::parse_po_header(r, result->msgstr.front());
163 throw parse_error(std::format(
"{}: Unknown .po syntax.",
token_location(token_it, path)));
171[[nodiscard]]
constexpr po_translations parse_po(std::string_view text, std::string_view path)
173 return parse_po(text.begin(), text.end(), path);
176[[nodiscard]]
inline po_translations parse_po(std::filesystem::path
const& path)
178 return parse_po(as_string_view(file_view{path}), path.string());
The HikoGUI namespace.
Definition array_generic.hpp:20
hi_export constexpr std::string token_location(It &it, ItEnd last, std::string_view path) noexcept
Create a location string for error messages.
Definition token.hpp:163
DOXYGEN BUG.
Definition algorithm_misc.hpp:20