Modulo:ChronoSpine/i18n
Vai alla navigazione
Vai alla ricerca
La documentazione per questo modulo può essere creata in Modulo:ChronoSpine/i18n/man
--[[
Module:ChronoSpine/i18n
=========================
Purpose
-------
The only place (besides /config and the `Cronologia` wrapper template)
where Italian text is allowed to live, per the "pure core" design rule
(project briefing, sec. 9.1). Three responsibilities:
1. Bilingual parameter names: WikiTrek uses Italian numbered
parameters (`data1`, `voce1`, ...); the shared code also accepts
English aliases (`date1`, `item1`, ...) so other wikis can use it
without needing their own translation of this file.
2. Turning an adapter's internal problem `code` (English, fixed
vocabulary -- see Module:ChronoSpine/manual) into an Italian
message an editor can actually read.
3. Turning parsed date/stardate/group-key DATA into Italian display
TEXT (M.format_date, M.format_stardate, M.group_label) -- used by
the renderer (Step 4). Locale affects word order and
capitalization, not just vocabulary, so the renderer never
assembles this text itself; it only asks this module for it.
Nothing here knows *how* a message or a formatted date ends up on the
page (a note box, a category, which CSS class wraps it, whether a
year becomes a wikilink, ...) -- that is the renderer's job. This
module only maps codes/field-names/data to text.
--]]
local M = {}
--[[
M.field_aliases
-----------------
Canonical field name -> ordered list of accepted parameter-name
prefixes (before the numeric suffix is appended). Order matters only
in the unusual case where an editor sets BOTH aliases for the same
numbered set with different values: Module:ChronoSpine/manual tries
them in this order and uses the first non-blank one, so the Italian
name wins on a conflict. This is a minor, deliberately low-stakes
tie-break, not a validated/flagged error case.
--]]
M.field_aliases = {
date = { "data", "date" },
label = { "voce", "item" },
stardate = { "stellare", "stardate" },
source = { "fonte", "source" },
note = { "nota", "note" },
group = { "gruppo", "group" },
category = { "categoria", "category" },
status = { "stato", "status" },
}
--[[
trim(text)
-----------
Private helper. Strips leading and trailing whitespace.
--]]
local function trim(text)
return text:match("^%s*(.-)%s*$")
end
--[[
M.resolve(args, field, index)
-------------------------------
Looks up one canonical `field` (e.g. "date") for one numbered set
`index` (e.g. 3), trying every alias in M.field_aliases[field] in
order (e.g. "data3", then "date3").
A value that is present but blank after trimming (`voce5 = ` with
nothing typed) is treated exactly like the parameter being entirely
absent: both return `nil`. Callers never need to special-case "set
but empty" versus "not set at all".
Parameters:
args (table) -- the raw frame arguments (frame:getParent().args
or equivalent): a plain table of parameter name ->
string value.
field (string) -- a canonical field name, a key of M.field_aliases.
index (number) -- the numbered set's index, e.g. 3 for "data3".
Returns:
(string|nil) -- the trimmed value, or nil if absent/blank, or if
`field` is not a known canonical name.
--]]
function M.resolve(args, field, index)
local aliases = M.field_aliases[field]
if not aliases then
return nil
end
for _, alias in ipairs(aliases) do
local value = args[alias .. tostring(index)]
if value ~= nil then
value = trim(tostring(value))
if value ~= "" then
return value
end
end
end
return nil
end
--[[
M.messages
-----------
Problem `code` -> Italian message template (a single `%s` placeholder).
Codes are namespaced by field ("date:...", "stardate:...") because the
same raw parser code (e.g. "bad-format") means a different thing
depending on which parser produced it. The two adapter-only codes
("missing-date", "missing-label") have no parser behind them at all --
there is nothing to parse when the parameter was never filled in.
This list only covers codes Module:ChronoSpine/manual can currently
produce (see that module for exactly which /date and /stardate error
codes are reachable through it). M.message() below falls back to a
generic message for anything not listed here, so adding a new adapter
or a new parser error code later cannot ever produce a hard failure --
at worst, an unhelpfully generic message until this table is extended.
--]]
M.messages = {
["missing-date"] = "Manca la data nel set numerato %s.",
["missing-label"] = "Manca il testo (voce) nel set numerato %s.",
["date:bad-format"] = "Formato data non riconosciuto: «%s».",
["date:bad-month"] = "Mese non valido nella data: «%s».",
["date:bad-day"] = "Giorno non valido nella data: «%s».",
["date:bad-range"] = "Intervallo di date non valido: «%s».",
["date:range-order"] = "Nell'intervallo «%s» la data finale precede quella iniziale.",
["stardate:bad-format"] = "Formato data siderale non riconosciuto: «%s».",
}
--[[
M.message(problem)
--------------------
Formats one problem table (as produced by Module:ChronoSpine/manual:
{ ordine, field, code, raw }) into a ready-to-display Italian string.
For codes that carry an offending raw value (`problem.raw`, e.g. a
malformed date string), that raw text fills the template's `%s`. For
the two "missing" codes, where there is no raw text at all (nothing
was typed), the numbered set's index (`problem.ordine`) is used
instead, so the message can still point the editor at which entry to
fix ("Manca la data nel set numerato 7.").
Parameters:
problem (table) -- { ordine = <number>, field = <string>,
code = <string>, raw = <string|nil> }
Returns:
(string) -- an Italian message, ready to show as-is.
--]]
function M.message(problem)
local template = M.messages[problem.code]
local placeholder = problem.raw or problem.ordine
if not template then
return string.format(
"Errore ChronoSpine non catalogato (set numerato %s, codice «%s»).",
tostring(problem.ordine), tostring(problem.code)
)
end
return string.format(template, placeholder)
end
--[[
M.month_names
--------------
Italian month names, indexed 1-12, used by M.format_date and
M.group_label below.
--]]
M.month_names = {
"gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno",
"luglio", "agosto", "settembre", "ottobre", "novembre", "dicembre",
}
--[[
format_one_date(date_fields)
------------------------------
Private helper shared by M.format_date and M.group_label. Formats ONE
anno/mese/giorno triple -- no `circa`, no `fine` -- cascading to
whichever fields are actually present:
{ anno = 2257 } -> "2257"
{ anno = 2022, mese = 5 } -> "maggio 2022"
{ anno = 2022, mese = 5, giorno = 5 } -> "5 maggio 2022"
This cascading is exactly what lets M.group_label (see below) reuse
this one function for "anno", "mese", AND "giorno" bucketing modes:
a group's key only ever contains the fields relevant to its mode, so
whichever subset is present drives the output automatically -- a
"mese"-mode, year-only bucket (key = { anno = 2022 }) falls through
to the plain "2022" case here with no special-casing needed.
Parameters:
date_fields (table) -- { anno, mese, giorno }; `anno` is always
present here (see M.format_date's docstring
for why an `incerta` date never reaches this
function at all).
Returns:
(string)
--]]
local function format_one_date(date_fields)
if date_fields.giorno and date_fields.mese then
return string.format(
"%d %s %d", date_fields.giorno, M.month_names[date_fields.mese], date_fields.anno
)
elseif date_fields.mese then
return string.format("%s %d", M.month_names[date_fields.mese], date_fields.anno)
else
return tostring(date_fields.anno)
end
end
--[[
format_single(date_fields)
-----------------------------
Private helper. Wraps format_one_date() with that SAME side's own
`circa` flag -- deliberately per-side, not a single flag for the
whole M.format_date call, since a range's two sides can each be
independently approximate (Module:ChronoSpine/date parses "c." on
either side of a range separately): "c. 2150-2200" and
"2150-c. 2200" are both valid parsed results, and must format
differently from each other.
--]]
local function format_single(date_fields)
local text = format_one_date(date_fields)
if date_fields.circa then
text = "c. " .. text
end
return text
end
--[[
M.format_date(date_fields)
-----------------------------
Formats a full date field set -- as carried on a normalised entry
(Module:ChronoSpine/entry) -- into Italian display text, handling
`circa` and `fine` (ranges) internally so the renderer never needs to
branch on either.
Only ever called for a date that actually HAS a year: an `incerta`
entry's anno/mese/giorno are all nil (per Module:ChronoSpine/entry's
contract, anno is nil only when incerta), and the renderer never
calls this function for one -- there is nothing to show in the date
slot for those; the "Data incerta" group heading (via M.group_label)
already says everything there is to say about them.
Parameters:
date_fields (table) -- { anno, mese, giorno, circa, fine }.
Returns:
(string) -- e.g. "2257", "c. 2150", "5 maggio 2022", "2256–2257",
"c. 2150–2200", "2150–c. 2200".
--]]
function M.format_date(date_fields)
local text = format_single(date_fields)
if date_fields.fine then
-- The en dash matches the preferred range separator accepted
-- by Module:ChronoSpine/date (see that module's RANGE_SEP_ENDASH),
-- for visual consistency between what an editor types and what
-- is displayed. Same explicit-UTF-8-bytes approach, same reason.
text = text .. "\226\128\147" .. format_single(date_fields.fine)
end
return text
end
--[[
M.format_stardate(value)
---------------------------
Formats a numeric stardate (as returned by
Module:ChronoSpine/stardate) into its narrativo-only display form.
Parameters:
value (number) -- e.g. 4789.6.
Returns:
(string) -- e.g. "DS 4789.6".
--]]
function M.format_stardate(value)
return "DS " .. tostring(value)
end
--[[
M.group_label(key, raggruppa, kind)
--------------------------------------
Turns one group's bucket key (as produced by Module:ChronoSpine/group)
into its display heading.
Parameters:
key (table) -- the group's `.key` (shape depends on
`raggruppa`; see Module:ChronoSpine/group).
raggruppa (string) -- "anno", "mese", "giorno", or "stagione".
kind (string) -- the group's `.kind`: "normal" or "incerta".
Checked FIRST and overrides everything else:
an `incerta` group's `.key` is always `{}`
regardless of `raggruppa`, so it needs its
own fixed heading rather than falling through
to the date/stagione logic below.
Returns:
(string) -- e.g. "2257", "maggio 2022", "5 maggio 2022", a
`gruppo` string verbatim, "Senza gruppo", or
"Data incerta".
--]]
function M.group_label(key, raggruppa, kind)
if kind == "incerta" then
return "Data incerta"
end
if raggruppa == "stagione" then
return key.gruppo or "Senza gruppo"
end
-- "anno", "mese", "giorno" (and any unrecognised value, which
-- Module:ChronoSpine/group itself already treats as "anno") all
-- share the same cascading year/month/day formatting: the key
-- only ever contains the fields relevant to the chosen mode, and
-- format_one_date already knows how to render whichever subset is
-- present -- see that function's docstring for why this reuse is
-- exactly correct, not a coincidence.
return format_one_date(key)
end
return M