summaryrefslogtreecommitdiff
path: root/src/application/i18n.php
diff options
context:
space:
mode:
Diffstat (limited to 'src/application/i18n.php')
-rw-r--r--src/application/i18n.php176
1 files changed, 152 insertions, 24 deletions
diff --git a/src/application/i18n.php b/src/application/i18n.php
index 2fcadab..f5b755d 100644
--- a/src/application/i18n.php
+++ b/src/application/i18n.php
@@ -1,72 +1,163 @@
<?php
+use RR\Shunt\Context;
+use RR\Shunt\Parser;
+use RR\Shunt\Scanner;
+
const MESSAGE_DIR = __DIR__ . "/messages";
$__i18n_msg_store = [];
+$__i18n_msg_store_plural = [];
+$__i18n_msg_metadata = [];
$__i18n_current_locale = null;
+const _I18N_MSGID = 0;
+const _I18N_MSGSTR = 1;
+const _I18N_MSGIDP = 2;
+const _I18N_MSGSTRP = 3;
+const _I18N_META = 10;
+
function i18n_parse(string $contents, ?string $filename = null): array {
$syntax_error = fn(string $msg, int $line): never =>
throw new Exception("i18n syntax error: $msg (in " . ($filename ?? "unknown") . ":" . $line . ")");
$other_error = fn(string $msg, int $line): never =>
throw new Exception("i18n error: $msg (in " . ($filename ?? "unknown") . ":" . $line . ")");
+ $metadata = [];
+
$msgs = [];
+ $pmsgs = [];
$lines = explode("\n", $contents);
$currentId = "";
$currentContext = "";
$currentMessage = "";
- $isInMessage = false;
+ $currentIdP = [];
+ $currentMessageP = [];
+ $currentMetadataBuffer = [];
+ $state = _I18N_MSGID;
+
+ $processedLines = 0;
foreach ($lines as $i => $ln) {
$lnNum = $i + 1;
if (trim($ln) === "")
continue;
+ if ($ln === "metadata({") {
+ if ($processedLines > 0)
+ $syntax_error("metadata must come first in file", $lnNum);
+ $currentMetadataBuffer []= "{";
+ $state = _I18N_META;
+ ++$processedLines;
+ continue;
+ }
+ if ($state == _I18N_META) {
+ if ($ln === "})") {
+ $currentMetadataBuffer []= "}";
+ $state = _I18N_MSGSTR;
+ $currentMetadataBuffer = implode("\n", $currentMetadataBuffer);
+ $metadata = json_decode($currentMetadataBuffer, true);
+ } else {
+ $currentMetadataBuffer []= $ln;
+ }
+ ++$processedLines;
+ continue;
+ }
+
switch ($ln[0]) {
case "#":
continue 2;
case ":":
- if ($currentId !== "") {
- if ($currentContext !== "")
- $currentId = $currentContext . "\004" . $currentId;
- if (isset($msgs[$currentId]))
- $other_error("duplicate message id '$currentId'", $lnNum);
- $msgs[$currentId] = $currentMessage;
+ if ($state === _I18N_MSGSTRP) {
+ // plural
+ if (count($currentIdP) > 0) {
+ if (isset($msgs[$currentId]))
+ $other_error("duplicate message id '$currentId'", $lnNum);
+ $pmsgs[$currentIdP[0]] = $currentMessageP;
+ }
+ } else {
+ // singular
+ if ($currentId !== "") {
+ if ($currentContext !== "")
+ $currentId = $currentContext . "\x7F" . $currentId;
+ if (isset($msgs[$currentId]))
+ $other_error("duplicate message id '$currentId'", $lnNum);
+ $msgs[$currentId] = $currentMessage;
+ }
}
- $currentId = json_decode(substr($ln, 2));
+ $currentIdP = [];
+ $currentMessageP = [];
+ $currentId = "";
$currentContext = "";
$currentMessage = "";
- $isInMessage = false;
+ if ($ln === ":...") {
+ // plural
+ $state = _I18N_MSGIDP;
+ } else {
+ // singular
+ $currentId = json_decode(substr($ln, 2)) ?? $syntax_error("malformed string", $lnNum);
+ $state = _I18N_MSGID;
+ }
+ break;
+ case "-":
+ if ($state === _I18N_MSGIDP) {
+ $_id = json_decode(substr($ln, 2)) ?? $syntax_error("malformed string", $lnNum);
+ if ($currentContext !== "")
+ $_id = $currentContext . "\x7F" . $_id;
+ $currentIdP []= $_id;
+ } elseif ($state === _I18N_MSGSTRP) {
+ $currentMessageP []= json_decode(substr($ln, 2)) ?? $syntax_error("malformed string", $lnNum);
+ } else
+ $syntax_error("cannot define multiple ids/messages in singular mode", $lnNum);
break;
case "=":
- $isInMessage = true;
- $currentMessage = json_decode(substr($ln, 2));
+ if ($ln === "=...") {
+ // plural
+ if ($state !== _I18N_MSGIDP)
+ $syntax_error("cannot start plural message in singular mode", $lnNum);
+ $state = _I18N_MSGSTRP;
+ } else {
+ // singular
+ $state = _I18N_MSGSTR;
+ $currentMessage = json_decode(substr($ln, 2)) ?? $syntax_error("malformed string", $lnNum);
+ }
break;
case "?":
- if ($isInMessage)
+ if ($state !== _I18N_MSGID)
$syntax_error("context must be defined before start of message", $lnNum);
- $currentContext = json_decode(substr($ln, 2));
+ $currentContext = json_decode(substr($ln, 2)) ?? $syntax_error("malformed string", $lnNum);
break;
case " ":
- if ($isInMessage)
- $currentMessage .= json_decode(substr($ln, 2));
+ if ($state === _I18N_MSGSTR)
+ $currentMessage .= json_decode(substr($ln, 2)) ?? $syntax_error("malformed string", $lnNum);
else
- $currentId .= json_decode(substr($ln, 2));
+ $currentId .= json_decode(substr($ln, 2)) ?? $syntax_error("malformed string", $lnNum);
break;
default:
$syntax_error("invalid start of line '" . $ln[0] . "' (0x" . str_pad(strtoupper(dechex(ord($ln[0]))), 2, "0", STR_PAD_LEFT) . ")", $lnNum);
break;
}
+
+ ++$processedLines;
}
- if ($currentId !== "") {
- if ($currentContext !== "")
- $currentId = $currentContext . "\x7F" . $currentId;
- if (isset($msgs[$currentId]))
- $other_error("duplicate message id '$currentId'", count($lines));
- $msgs[$currentId] = $currentMessage;
+ if ($state === _I18N_MSGSTRP) {
+ // plural
+ if (count($currentIdP) > 0) {
+ if (isset($msgs[$currentId]))
+ $other_error("duplicate message id '$currentId'", $lnNum);
+ $pmsgs[$currentIdP[0]] = $currentMessageP;
+ }
+ } else {
+ // singular
+ if ($currentId !== "") {
+ if ($currentContext !== "")
+ $currentId = $currentContext . "\x7F" . $currentId;
+ if (isset($msgs[$currentId]))
+ $other_error("duplicate message id '$currentId'", $lnNum);
+ $msgs[$currentId] = $currentMessage;
+ }
}
- return $msgs;
+ return [$metadata, $msgs, $pmsgs];
}
function i18n_locale(string $locale) {
@@ -91,14 +182,51 @@ function i18n_get(string $msgid, array $params = [], ?string $context = null): s
);
}
+function i18n_get_plural(string $msgid_singular, string $msgid_plural, int $count, array $params = [], ?string $context = null): string {
+ global $__i18n_current_locale, $__i18n_msg_metadata, $__i18n_msg_store_plural;
+
+ $key = $msgid_singular;
+ if ($context !== null)
+ $key = $context . "\x7F" . $key;
+
+ $msgs = ($__i18n_msg_store_plural[$__i18n_current_locale] ?? [])[$msgid_singular] ?? null;
+ $msg = "";
+ if ($msgs === null) {
+ $msg = $count === 1 ? $msgid_singular : $msgid_plural;
+ } else {
+ $expression = (($__i18n_msg_metadata[$__i18n_current_locale] ?? [])["plural"] ?? [])["indexMapping"] ?? "n == 1 ? 0 : 1";
+ $ctx = new Context();
+ $ctx->def("ifelse", function(bool $c, $a, $b) { return $c ? $a : $b; });
+ $ctx->def("n", $count);
+ $parser = new Parser(new Scanner($expression));
+ $index = $parser->reduce($ctx);
+ $index = max(0, min(count($msgs), $index));
+ $msg = $msgs[$index];
+ }
+
+ uksort($params, fn(string $a, string $b): int => strlen($b) <=> strlen($a));
+ return str_replace(
+ array_map(fn(string $k): string => "%$k%", array_keys($params)),
+ array_values($params),
+ $msg
+ );
+}
+
function __(string $msgid, array $params = [], ?string $context = null): string {
return i18n_get($msgid, $params, $context);
}
+function ___(string $msgid_singular, string $msgid_plural, int $count, array $params = [], ?string $context = null): string {
+ return i18n_get_plural($msgid_singular, $msgid_plural, $count, $params, $context);
+}
+
foreach (scandir(MESSAGE_DIR) as $ent) {
$path = MESSAGE_DIR . "/" . $ent;
if ($ent[0] === "." || !is_file($path) || strcasecmp(pathinfo($ent, PATHINFO_EXTENSION), "msg") !== 0)
continue;
$lang = pathinfo($ent, PATHINFO_FILENAME);
- $__i18n_msg_store[$lang] = i18n_parse(file_get_contents($path), $ent);
+ [$meta, $msgs, $pmsgs] = i18n_parse(file_get_contents($path), $ent);
+ $__i18n_msg_store[$lang] = $msgs;
+ $__i18n_msg_store_plural[$lang] = $pmsgs;
+ $__i18n_msg_metadata[$lang] = $meta;
}