7#include "translation.hpp"
8#include "../i18n/i18n.hpp"
10#include "../parser/parser.hpp"
11#include "../macros.hpp"
17hi_export_module(hikogui.l10n.po_parser);
22 std::optional<std::string> msgctxt;
29 language_tag language;
30 size_t nr_plural_forms;
37template<std::input_iterator It, std::sentinel_for<It> ItEnd>
40 hi_assert(it != last);
43 if ((*it == token::id)) {
47 std::format(
"{}: Expecting a keyword at start of each line, got {}",
token_location(it, last, path), *it));
54 if (it != last and *it == token::integer) {
55 index =
static_cast<size_t>(*it++);
57 throw parse_error(std::format(
58 "{}: Expecting an integer literal as an index for {}, got {}",
token_location(it, last, path), name, *it));
61 if (it != last and *it ==
']') {
64 throw parse_error(std::format(
65 "{}: The index on {} must terminate with a bracket ']', got {}",
token_location(it, last, path), name, *it));
70 if (it != last and (*it == token::sstr or *it == token::dstr)) {
74 std::format(
"{}: Expecting a string value after {}, got {}",
token_location(it, last, path), name, *it));
77 while (it != last and (*it == token::sstr or *it == token::dstr)) {
82 return {name, index, value};
85template<std::input_iterator It, std::sentinel_for<It> ItEnd>
86[[nodiscard]]
constexpr std::optional<po_translation> parse_po_translation(It& it, ItEnd last, std::string_view path)
91 if (r.msgstr.empty()) {
93 auto [name, index, value] = parse_po_line(it, last, path);
95 if (name ==
"msgctxt") {
98 }
else if (name ==
"msgid") {
101 }
else if (name ==
"msgid_plural") {
102 r.msgid_plural = value;
104 }
else if (name ==
"msgstr") {
105 if (index >= r.msgstr.size()) {
106 r.msgstr.resize(index + 1);
108 r.msgstr[index] = value;
112 std::format(
"{}: Line starts with unexpected keyword {}",
token_location(it, last, path), name));
115 }
else if ((*it == token::id) and (*it ==
"msgstr")) {
117 auto [name, index, value] = parse_po_line(it, last, path);
119 if (index >= r.msgstr.size()) {
120 r.msgstr.resize(index + 1);
122 r.msgstr[index] = value;
133constexpr void parse_po_header(po_translations& r, std::string_view header)
135 using namespace std::literals;
137 for (hilet line :
std::views::split(header,
"\\n"sv)) {
143 auto split_line = make_vector<std::string_view>(std::views::split(line,
":"sv));
144 if (split_line.size() < 2) {
145 throw parse_error(std::format(
"Unknown header '{}'", std::string_view{line}));
148 hilet name = to_lower(strip(split_line.front()));
149 split_line.erase(split_line.begin());
150 hilet value = strip(join(split_line,
":"));
152 if (name ==
"language") {
153 r.language = language_tag{value};
160template<std::input_iterator It, std::sentinel_for<It> ItEnd>
161[[
nodiscard]]
constexpr po_translations parse_po(
It it, ItEnd last, std::string_view path)
165 auto token_it = lexer<lexer_config::sh_style()>.parse(
it, last);
167 while (
token_it != std::default_sentinel) {
168 if (
auto result = detail::parse_po_translation(
token_it, std::default_sentinel, path)) {
170 r.translations.push_back(*
result);
172 }
else if (
result->msgstr.size() == 1) {
174 detail::parse_po_header(r,
result->msgstr.front());
185[[
nodiscard]]
constexpr po_translations parse_po(std::string_view text, std::string_view path)
187 return parse_po(text.begin(), text.end(), path);
190[[
nodiscard]]
inline po_translations parse_po(std::filesystem::path
const& path)
192 return parse_po(as_string_view(file_view{path}), path.string());
DOXYGEN BUG.
Definition algorithm.hpp:16
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:160
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
Definition po_parser.hpp:21
Definition po_parser.hpp:28