Wikiorðabók iswiktionary https://is.wiktionary.org/wiki/Wikior%C3%B0ab%C3%B3k:Fors%C3%AD%C3%B0a MediaWiki 1.47.0-wmf.23 case-sensitive Miðill Kerfissíða Spjall Notandi Notandaspjall Wikiorðabók Wikiorðabókarspjall Mynd Myndaspjall Melding Meldingarspjall Snið Sniðaspjall Hjálp Hjálparspjall Flokkur Flokkaspjall Viðauki Viðaukaspjall Samheitasafn Samheitasafnsspjall Skjátexti Skjátextaspjall Module Module talk Event Event talk skipun 0 80768 393943 360828 2026-10-08T15:14:40Z ~2026-54289-49 9775 393943 wikitext text/x-wiki {{-is-}} {{-is-nafnorð-}} '''skipun''' {{kvk.}} :[[pöntun]], [[niðurröðun]] {{-þýðingar-}} {{þýðingar |en= {{þýðing|en|order}}, {{þýðing|en|command}} |it= {{þýðing|it|ordine}}, {{þýðing|it|comando}} |pt= {{þýðing|pt|ordem}}, {{þýðing|pt|comando}} |fr= {{þýðing|fr|}} |es= {{þýðing|es|}} |de= {{þýðing|de|Ordnung}}, {{þýðing|de|Befehl}} }} {{-tilvísun-}} {{Wikipedia|skipun}} {{Ref-IcelandicOnlineDictionary|skipun}} {{Ref-Orðabók Háskólans|skipun}} rr9j7vg7npgx40gzwo3gj12y7tjvgzy Snið:is-fallbeyging 10 119014 393945 392812 2026-10-08T19:29:26Z Ow! That Hurts! 8952 skapað 393945 wikitext text/x-wiki {{#invoke:is-nafnorð|show}} qj3v4clzv75cz7sp4zptyw8xpfkpwvf Module:is-nafnorð 828 119298 393944 2026-10-08T19:27:24Z Ow! That Hurts! 8952 skapað 393944 Scribunto text/plain local export = {} --[=[ Authorship: Ben Wing <benwing2> ]=] --[=[ TERMINOLOGY: -- "slot" = A particular combination of case/number. Example slot names for nouns are "acc_s" (accusative singular) and "gen_p" (genitive plural). Each slot is filled with zero or more forms. -- "form" = The declined Icelandic form representing the value of a given slot. -- "lemma" = The dictionary form of a given Icelandic term. Generally the nominative singular, or nominative plural of plural-only nouns, but may occasionally be another form if the nominative is missing. ]=] --[=[ FIXME: 1. Support 'plstem' overrides. [DONE] 2. Support definite lemmas such as [[Bandaríkin]] "the United States". [DONE] 3. Support adjectivally-declined terms. [DONE PARTIALLY] 4. Support @ for built-in irregular lemmas. [DONE; SINCE REPLACED WITH SCRAPING SPECS] 5. Somehow if the user specifies v-infix, it should prevent default unumut from happening in strong feminines. [DONE] 6. Def acc pl should ignore -u ending in indef acc pl. [DONE] 7. Footnotes on omitted forms should be possible. 8. Remove setting of override on pl when processing plural-only terms in synthesize_singular_lemma(); interferes with decllemma in [[dyr]]. But then need to fix handling of masculine accusative plural. [DONE] 9. Rationalize conventions used in u-mutation types. [DONE] 10. Compute defaulted number and definiteness early so it's usable when merging built-in and user-specified specs. [DONE] 11. Support multiple declension specs. [DONE] 12. Support dark mode. [DONE] 13. Support scraping declension specs. [DONE] 14. Support @-d etc. for suffix scraping. [DONE] 15. Include scraped base nouns in title annotation. [DONE] 16. Support @@ for self-scraping. [DONE IN [[Module:gmq-headword]]] 17. Support scraping multiple declension specs; e.g. [[fræði]] is declared with 'n.pl|f.sg' and [[hljómfræði]] would like to use '@f' but you get an error. ]=] local lang = require("Module:languages").getByCode("is") local require_when_needed = require("Module:utilities/require when needed") local m_table = require("Module:table") local m_links = require("Module:links") local m_string_utilities = require("Module:string utilities") local iut = require("Module:inflection utilities") local put = require("Module:parse utilities") local m_para = require("Module:parameters") local com = require("Module:is-common") local m_inflection_table = require("Module:inflection-table") local m_is_adjective = require_when_needed("Module:is-adjective") local en_utilities_module = "Module:en-utilities" local u = mw.ustring.char local rsplit = mw.text.split local rfind = mw.ustring.find local rmatch = mw.ustring.match local rsubn = mw.ustring.gsub local ulen = mw.ustring.len local usub = mw.ustring.sub local uupper = mw.ustring.upper local ulower = mw.ustring.lower local dump = mw.dumpObject local force_cat = false -- set to true to make categories appear in non-mainspace pages, for testing local SUB_ESCAPED_PERIOD = u(0xFFF0) local SUB_ESCAPED_COMMA = u(0xFFF1) -- version of rsubn() that discards all but the first return value local function rsub(term, foo, bar) local retval = rsubn(term, foo, bar) return retval end -- version of rsubn() that returns a 2nd argument boolean indicating whether -- a substitution was made. local function rsubb(term, foo, bar) local retval, nsubs = rsubn(term, foo, bar) return retval, nsubs > 0 end local function track(track_id) require("Module:debug/track")("is-noun/" .. track_id) return true end local potential_lemma_slots = { "ind_nom_s", "ind_nom_p", "def_nom_s", "def_nom_p", "ind_acc_s", -- for [[sig]] } local cases = { "nom", "acc", "dat", "gen", } local case_set = m_table.listToSet(cases) local overridable_stems = { "stem", "vstem", "plstem", "plvstem", "imutval", "unimutval", } local overridable_stem_set = m_table.listToSet(overridable_stems) local control_specs = { "umut", "imut", "unumut", "unimut", "con", "defcon", "j", "v", } local control_spec_set = m_table.listToSet(control_specs) local clitic_articles = { m = { nom_s = "inn", acc_s = "inn", dat_s = "num", gen_s = "ins", nom_p = "nir", acc_p = "na", dat_p = "num", gen_p = "nna", }, f = { nom_s = "in", acc_s = "ina", dat_s = "inni", gen_s = "innar", nom_p = "nar", acc_p = "nar", dat_p = "num", gen_p = "nna", }, n = { nom_s = "ið", acc_s = "ið", dat_s = "nu", gen_s = "ins", nom_p = "in", acc_p = "in", dat_p = "num", gen_p = "nna", }, } local gender_code_to_desc = { m = "masculine", f = "feminine", n = "neuter", none = nil, } local number_code_to_desc = { sg = "singular", pl = "plural", both = "both numbers", none = nil, } local definiteness_code_to_desc = { indef = "indefinite-only", def = "definite-only", bothdef = "indefinite and definite", none = nil, } local function get_noun_slots(alternant_multiword_spec) local noun_slots_list = {} for _, case in ipairs(cases) do for _, num in ipairs { "s", "p" } do for _, def in ipairs { "ind", "def" } do local slot = ("%s_%s_%s"):format(def, case, num) local accel = ("%s|%s"):format(def == "ind" and "indef" or def, case) if alternant_multiword_spec.actual_number == "both" then accel = accel .. "|" .. num end table.insert(noun_slots_list, { slot, accel }) end end end for _, potential_lemma_slot in ipairs(potential_lemma_slots) do table.insert(noun_slots_list, { potential_lemma_slot .. "_linked", "-" }) end return noun_slots_list end local function generate_list_of_possibilities_for_err(list) local quoted_list = {} for _, item in pairs(list) do if item == "" then item = "<nowiki />" end table.insert(quoted_list, "'" .. item .. "'") end table.sort(quoted_list) return mw.text.listToText(quoted_list) end local function skip_slot(number, definiteness, slot) return number == "sg" and slot:find("_p$") or number == "pl" and slot:find("_s$") or definiteness == "def" and slot:find("^ind_") or (definiteness == "indef" or definiteness == "none") and slot:find("^def_") end local function apply_i_mutation(stem, newv) return com.apply_i_mutation(stem, newv, "exclude final -ur", "error if unmatchable") end local function apply_reverse_i_mutation(stem, newv, error_if_unmatchable) return com.apply_reverse_i_mutation(stem, newv, "exclude final -ur", error_if_unmatchable) end local function apply_u_mutation(stem, typ, error_if_unmatchable) return com.apply_u_mutation(stem, typ, "exclude final -ur", error_if_unmatchable) end local function apply_reverse_u_mutation(stem, typ, error_if_unmatchable) return com.apply_reverse_u_mutation(stem, typ, "exclude final -ur", error_if_unmatchable) end --[=[ Create an empty `base` object for holding the result of parsing and later the generated forms. The object is of the form { -- Original lemma as directly given by the user or taken from the pagename. orig_lemma = "ORIGINAL-LEMMA", -- Same as `orig_lemma` but with links removed. orig_lemma_no_links = "ORIGINAL-LEMMA-NO-LINKS", -- Originally the same as `orig_lemma_no_links`, but if the term is a plural-only noun, this will be the corresponding -- singular lemma, and if the term is an adjective form, this will be the corresponding lemma (strong nominative -- masculine singular form). lemma = "LEMMA", -- Generated per-slot forms. After calling `inflect_multiword_or_alternant_multiword_spec`, the forms will be filled -- in with the format as given below, where the value of each slot is a form object. After calling `show_forms`, the -- value of each slot will be a formatted string listing all of the forms of that slot, or "—" if there are none. forms = { SLOT = { { form = "FORM", footnotes = nil or {"FOOTNOTE", "FOOTNOTE", ...}, }, ... }, ... }, -- Specs for control groups as specified by the user. CONTROL_GROUP is as below and CONTROL_SPEC is -- {form = "FORM", footnotes = nil or {"FOOTNOTE", "FOOTNOTE", ...}, defaulted = BOOLEAN}, where FORM is as specified -- by the user (e.g. "uUmut", "-unumut") or set as a default by the code (in which case `defaulted` will be set to -- true for control groups "umut" and "unumut"). The control groups are as follows: -- * umut (u-mutation); -- * imut (i-mutation); -- * unumut (reverse u-mutation); -- * unimut (reverse i-mutation); -- * con (stem contraction before vowel-initial endings); -- * defcon (stem contraction before vowel-initial definite clitics when the ending itself is null); -- * j (j-infix before vowel-initial endings not beginning with an i); -- * v (v-infix before vowel-initial endings). CONTROL_GROUP = { CONTROL_SPEC, CONTROL_SPEC, ... }, -- Property sets containing computed stems, one per each combination of control group values. Described in more detail -- below. prop_sets = { PROPSET, -- see below ..., }, -- Per-slot overrides, which override forms generated by the auto-determined or specified declension pattern. SLOT is -- the actual name of the slot, normally without the definiteness prefix, such as "dat_s" (NOT the slot name as -- specified by the user, which would be just "dat" for "dat_s") and OVERRIDE is of the form -- {indef = {FORMOBJ, FORMOBJ, ...}, def = nil or false or {FORMOBJ, FORMOBJ, ...}}, where FORMOBJ is of the form -- {form = FORM, footnotes = FOOTNOTES} as in the `forms` table ("-" means to suppress the slot entirely and is -- signaled by "--" as the user-specified form value; normally FORM values are endings, but a value preceded by ! -- means it's a full form rather than an ending; in such forms you can use # to indicate the lemma and ## to indicate -- the lemma minus -ur or -r, as with stems); `indef` means the override(s) of the indefinite variant of the slot and -- is specified by the user before a slash; `def` means the override(s) of the definite variant of the slot and come -- after a slash, and `false` for either means that the user left the value before or after the slash completely -- blank, meaning not to override the indefinite or definite forms. Sometimes the slot itself has def_ in it; this -- happens when the user preceded the slot spec by 'def', e.g. 'defdat' or 'defgenpl'. overrides = { SLOT = OVERRIDE, SLOT = OVERRIDE, ... }, -- Overrides for the genitive singular, specified after a comma after the gender. OVERRIDE is in the same format as -- above. gens = nil or OVERRIDE, -- Overrides for the nominative and accusative plural, specified after a comma after the gender. OVERRIDE is in the -- same format as above. The actual values given are for the nominative plural, and the accusative plural is derived -- automatically from these values. pls = nil or OVERRIDE, -- "sg", "pl", "both" or "none" (for certain pronouns); may be missing and if so is defaulted number = "NUMBER", -- "m", "f", "n" or "none" (for certain pronouns); always specified by the user gender = "GENDER", -- "def", "indef", "bothdef" or "none" (for pronouns); may be missing and if so is defaulted definiteness = "DEFINITENESS", -- decline like the specified lemma decllemma = nil or "DECLLEMMA", -- decline like the specified gender declgender = nil or "DECLGENDER", -- decline like the specified number declnumber = nil or "DECLNUMBER", -- override the stem; may have # (= lemma) or ## (= lemma minus -ur or -r) stem = nil or "STEM", -- override the stem used before vowel-initial endings; same format as `stem` vstem = nil or "STEM", -- override the plural stem; same format as `stem` plstem = nil or "STEM", -- override the plural stem used before vowel-initial endings; same format as `stem` plvstem = nil or "STEM", -- decline like an adjective; will be present if the user gave a spec starting with 'adj' adjspec = { -- User explicitly specified the lemma using a colon + lemma. lemma = nil or LEMMA -- User gave a one-part or two-part substitution spec such as 'tvöfalt<adj/dur>' or 'ryðfrítt<adj/tt/r>'. subspec = nil or { from = nil or FROM, to = TO, }, }, -- misc Boolean properties: -- * "proper" (a lowercase noun that behaves like a proper noun, i.e. defaults to no plural or definite forms); -- * "common" (a capitalized noun that behaves like a common noun, i.e. defaults to plural and definite forms); -- * "dem" (a demonym, i.e. a capitalized noun such as [[Svisslendingur]] "Swiss person" that behaves like a common -- noun; currently behaves like "common"); -- * "builtin" (for built-in terms such as pronouns); -- * "indecl" (noun is indeclinable); -- * "decl?" (declension is unknown); -- * "iending" (a definite-only noun whose lemma ends in -i, which is elided before a definite clitic beginning with -- i-); -- * "rstem" (an r-stem like [[bróðir]] "brother" or [[dóttir]] "daughter"); -- * "já" (a neuter in -é whose stem alternates with -já, such as [[tré]] "tree" and [[hné]]/[[kné]] "knee"); -- * "weak" (the noun should decline like an ordinary weak noun; used in the declension of [[fjandi]] to disable the -- special -ndi declension); -- * "linkasis" (when linking definite-only and plural-only lemmas in the headword, link as-is instead of -- linking the singular indefinite version); props = { PROP = true, PROP = true, ... }, -- Alternant-level footnotes, specified using `.[footnote]`, i.e. a footnote by itself. footnotes = nil or {"FOOTNOTE", "FOOTNOTE", ...}, -- ADDNOTE_SPEC is {slot_specs = {"SPEC", "SPEC", ...}, footnotes = {"FOOTNOTE", "FOOTNOTE", ...}}; SPEC is a Lua -- pattern matching slots (anchored on both sides) and FOOTNOTE is a footnote to add to those slots. addnote_specs = { ADDNOTE_SPEC, ADDNOTE_SPEC, ... }, } There is one PROPSET (property set) for each combination of control specs; in the lower limit, there is a single property set. There may be more than one property set e.g. if the user specified 'umut,uUmut' or '-j,j' or '-imut,imut' or some combination of these. The properties in a given property set specify the values themselves of each control group, as well as stems (derived from the control specs) that are used to construct the various forms and populate the slots in `forms` with these values. The information found in the property sets cannot be stored in `base` because it depends on a particular combination of control specs, of which there may be more than one (see above). The decline_noun() function iterates over all property sets and calls the appropriate declension function on each one in turn, which adds forms to each slot in `base.forms`, automatically deduplicating. The properties in each property set are: * Control specs: These are copied from the control specs at the base level. The key is one of the possible control groups ("umut", "imut", "con", etc.), but the value is a single form object {form = "FORM", footnotes = nil or {"FOOTNOTE", "FOOTNOTE", ...}}. These are set by expand_property_sets(). * Stems (each stem is either a string or a form object; stems in general may be missing, i.e. nil, unless otherwise specified, and default to more general variants): ** `stem`: The basic stem. Always set. May be overridden by more specific variants. ** `nonvstem`: The stem used when the ending is null or starts with a consonant, unless overridden by a more specific variant. Defaults to `stem`. Not currently used, but could be if e.g. a user stem override `nonvstem:...` were supported. ** `umut_nonvstem`: The stem used when the ending is null or starts with a consonant and u-mutation is in effect, unless overridden by a more specific variant. Defaults to `nonvstem`. Will only be present when the result of u-mutation is different from the stem to which u-mutation is applied. (In this case, it will be present even if `nonvstem` is missing, because there is no generic `umut_stem`.) ** `imut_nonvstem`: The stem used when the ending is null or starts with a consonant and i-mutation is in effect. If i-mutation is in effect, this should always be specified (otherwise an internal error will occur); hence it has no default. Note that i-mutation is only in effect when either (a) `imut` or `unimut` was specified; (b) a user-specified override is given that begins with a single ^ (indicating i-mutation); or (c) a declension type is in effect that contains default endings beginning with a single ^ (examples are `f-long-vowel` for lemmas in -ó and `f-long-umlaut-vowel-r`). Note also that this will be present even if `nonvstem` is missing, because there is no generic `imut_stem`. ** `vstem`: The stem used when the ending starts with a vowel, unless overridden by a more specific variant. Defaults to `stem`. Will be specified when contraction is in effect or the user specified `vstem:...`. ** `umut_vstem`: The stem(s) used when the ending starts with a vowel and u-mutation is in effect. Defaults to `vstem`. Note that u-mutation applies to the contracted stem if both u-mutation and contraction are in effect. Will only be present when the result of u-mutation is different from the stem to which u-mutation is applied. (In this case, it will be present even if `vstem` is missing, because there is no generic `umut_stem`.) ** `imut_vstem`: The stem(s) used when the ending starts with a vowel and i-mutation is in effect. If i-mutation is in effect, this should always be specified (otherwise an internal error will occur); hence it has no default. Note that i-mutation applies to the contracted stem if both i-mutation and contraction are in effect. See `imut_nonvstem` for comments on when this stem will be present. ** `null_defvstem`: The stem(s) used when the ending is null and is followed by a definite ending that begins with a vowel, unless overridden by a more specific variant. Defaults to `nonvstem`. This is normally set when `defcon` is specified. ** `umut_null_defvstem`: The stem(s) used when the ending is null and is followed by a definite ending that begins with a vowel, and u-mutation is in effect. Defaults to `null_defvstem`. This is normally set when `defcon` is specified and u-mutation is needed, as in the nom/acc pl of neuter [[mastur]] "mast". Will only be present when the result of u-mutation is different from the stem to which u-mutation is applied. ** `pl_stem`: The basic stem used for plural inflections. Only set when `plstem:...` is specified by the user. If this is set, the alternative plural-specific stem variants are used, where each of the above stems has a plural-specific counterpart, and the identical algorithms and fallbacks are used to determine the correct stem. ** `pl_nonvstem`, `pl_umut_nonvstem`, `pl_imut_nonvstem`, `pl_vstem`, `pl_umut_vstem`, `pl_imut_vstem`, `pl_null_defvstem`, `pl_umut_null_defvstem`: Plural-specific counterparts of the above stems. See the comment under `pl_stem` for when these are used. * Other properties: ** `jinfix`: If present, either "" or "j". Inserted between the stem and ending when the ending begins with a vowel other than "i". Note that j-infixes don't apply to ending overrides. ** `jinfix_footnotes`: Footnotes to attach to forms where j-infixing is possible (even if it's not present). ** `vinfix`: If present, either "" or "v". Inserted between the stem and ending when the ending begins with a vowel. Note that v-infixes don't apply to ending overrides. `jinfix` and `vinfix` cannot both be specified. ** `vinfix_footnotes`: Footnotes to attach to forms where v-infixing is possible (even if it's not present). ** `imut`: If specified (i.e. not nil), either true or false. If specified, there may be associated footnotes in `imut_footnotes`. If true, i-mutation and associated footnotes are in effect before endings starting with "i". If false, associated footnotes still apply before endings starting with "i". Note that i-mutation is also in effect if the ending has ^ prepended, but the associated footnotes don't apply here. ** `imut_footnotes`: See `imut`. ** `unumut`: If specified (i.e. not nil), the type of un-u-mutation requested (either "unumut" or a variant, or the negation of the same using "-unumut" or a variant for no un-u-mutation; "unumut" and variants differ in which slots any associated footnote are placed). If specified, there may be associated footnotes in `unumut_footnotes`. If "unumut" itself, u-mutation is in effect *except* before an ending that starts with an "a" or "i" (unless i-mutation is in effect, which takes precedence). If any other variant, the rules are different: when masculine, u-mutation is in effect *except* in the gen sg and pl (examples are [[söfnuður]] "congregation" and [[mánuður]] "month"); when feminine, u-mutation is in effect except in the nom/acc/gen pl (examples are [[verslun]] "trade, business; store, shop" and [[kvörtun]] "complaint"). When u-mutation is *not* in effect, and i-mutation is also not in effect, the associated footnotes in `unumut_footnotes` apply. If `unumut` is "-unumut" or a variant, there is no un-u-mutation (i.e. there are no special u-mutated stems, and the basic stems, which typically have u-mutation built into them, apply throughout), but the associated footnotes in `unumut_footnotes` still apply in the same circumstances where they would apply if `unumut` were the non-negated counterpart. ** `unumut_footnotes`: See `unumut`. ** `unimut`: If specified (i.e. not nil), either true or false. If specified, there may be associated footnotes in `unimut_footnotes`. If true, i-mutation is in effect *except* in certain case/num combinations that depend on the gender. Specifically: (1) for masculine nouns e.g. [[ketill]] "kettle" and proper names [[Egill]] and [[Ketill]], i-mutation does not apply in the dat sg and throughout the plural; (2) for feminine nouns e.g. [[kýr]] "cow", [[sýr]] "sow (archaic)" and [[ær]] "ewe", i-mutation does not apply in the acc and dat sg and in the dat and gen pl. Cf. also feminine pl-only [[hættur]] "bedtime, quitting time" and [[mætur]] "appreciation, liking", which use 'unimut' to get e.g. dat pl [[háttum]] and gen pl [[hátta]]; but these are handled by synthesizing a singular without i-mutation in the lemma. Very similar are neuter pl [[læti]] "behavior, demeanor" and [[ólæti]] "noise, racket", with e.g. dat pl [[látum]] and gen pl [[láta]], which are handled in the same way. When i-mutation is *not* in effect, the associated footnotes in `unimut_footnotes` apply. If false, the associated footnotes in `unimut_footnotes` still apply in the same circumstances where they would apply if `unimut` where true. ** `unimut_footnotes`: See `unimut`. ]=] local function create_base() return { forms = {}, overrides = {}, props = {}, addnote_specs = {}, } end -- Return true if `stem` refers to a proper noun (first character is uppercase, second character is lowercase). local function is_proper_noun(base, stem) if base.props.common or base.props.dem then return false end if base.props.proper then return true end if base.source_template == "is-noun" then return false end if base.source_template == "is-proper noun" then return true end local first_letter = usub(stem, 1, 1) local second_letter = usub(stem, 2, 2) return ulower(first_letter) ~= first_letter and ((not second_letter or second_letter == "") or uupper(second_letter) ~= second_letter) end --[=[ Basic function to combine stem(s) and other properties with ending(s) and insert the result into the appropriate slot. `base` is the object describing all the properties of the word being inflected for a single alternant (in case there are multiple alternants specified using `((...))`). `slot_prefix` is either "ind_" or "def_" and is prefixed to the slot value in `slot` to get the actual slot to add the resulting forms to. (`slot_prefix` is separated out because the code below frequently needs to conditionalize on the value of `slot` and should not have to worry about the definite and indefinite slot variants). `props` is a property set object containing computed stems and other information (such as whether i-mutation is active) about a particular combination of control specs. See the comment above create_base() for more information. The information found in `props` cannot be stored in `base` because there may be more than one set of such properties per `base` (e.g. if the user specified 'umut,uUmut' or '-j,j' or '-imut,imut' or some combination of these; in such a case, the caller will iterate over all possible combinations, and ultimately invoke add() multiple times, one per combination). `endings` is the ending or endings added to the appropriate stem (after any j or v infix) to get the form(s) to add to the slot. Its value can be a single string, a list of strings, or a list of form objects (i.e. in general list form). `clitics` is the clitic or clitics to add after the endings to form the actual form value inserted into definite slots; it should be nil for indefinite slots. Its format is the same as for `endings`. `ending_override`, if true, indicates that the ending(s) supplied in `endings` come from a user-specified override, and hence j and v infixes should not be added as they are already included in the override if needed. ]=] local function add_slotval(base, slot_prefix, slot, props, endings, clitics, ending_override) if not endings then return end -- Call skip_slot() based on the declined number and definiteness; if the actual number is different, we correct -- this in decline_noun() at the end. if skip_slot(base.number, base.definiteness, slot) then return end if not clitics then clitics = { "" } elseif type(clitics) == "string" then clitics = { clitics } end if type(endings) == "string" then endings = { endings } end -- Loop over each ending and clitic. for _, endingobj in ipairs(endings) do for _, cliticobj in ipairs(clitics) do -- Do the following inside of the innermost loop even though it does not depend on the value of `cliticobj`, -- because that way we are free to mutate `ending` below. local ending, ending_footnotes if type(endingobj) == "string" then ending = endingobj else ending = endingobj.form ending_footnotes = endingobj.footnotes end -- Ending of "-" means the user used -- to indicate there should be no form here. if ending == "-" then return end local function interr(msg) error(("Internal error: For lemma '%s', slot '%s%s', ending '%s', %s: %s"):format(base.lemma, slot_prefix, slot, ending, msg, dump(props))) end local clitic, clitic_footnotes if type(cliticobj) == "string" then clitic = cliticobj else clitic = cliticobj.form clitic_footnotes = cliticobj.footnotes end -- Compute whether i-mutation or u-mutation is in effect, and compute the "mutation footnotes", which are -- footnotes attached to a mutation-related indicator and which may need to be added even if no mutation is -- in effect (specifically when dealing with an ending that would trigger a mutation if in effect). AFAIK -- you cannot have both mutations in effect at once, and i-mutation overrides u-mutation if both would be in -- effect. -- Single ^ at the beginning of an ending indicates that the i-mutated version of the stem should apply, and -- double ^^ at the beginning indicates that the u-mutated version should apply. local explicit_imut, explicit_umut -- % at the end of a definite ending indicates that the following i- of the clitic should drop, as with -- neuter [[tré]], [[kné]], [[fé]]. There's no counterpart to force irregular inclusion of an i- that would -- normally drop; just include it in the ending (as with acc/dat sg of [[eygló]] "eyeball???" and [[sígó]] -- "cig"). local clitic_i_drops ending, explicit_umut = rsubb(ending, "^%^%^", "") if not explicit_umut then ending, explicit_imut = rsubb(ending, "^%^", "") end ending, clitic_i_drops = rsubb(ending, "%%$", "") local is_vowel_ending = rfind(ending, "^" .. com.vowel_c) local is_vowel_clitic = rfind(clitic, "^" .. com.vowel_c) local mut_in_effect, mut_not_in_effect, mut_footnotes local ending_in_a = not not ending:find("^a") local ending_in_i = not not ending:find("^i") local ending_in_u = not not ending:find("^u") if props.unimut ~= nil and props.unumut ~= nil then interr("Cannot have both 'unimut' and 'unumut' in effect at the same time") end if props.unimut ~= nil and props.imut ~= nil then interr("Cannot have both 'unimut' and 'imut' in effect at the same time") end if props.unumut ~= nil and props.umut ~= nil then interr("Cannot have both 'unumut' and 'umut' in effect at the same time") end if explicit_imut then mut_in_effect = "i" elseif explicit_umut then mut_in_effect = "u" else if props.unimut ~= nil then local is_unimut_slot if base.gender == "m" then is_unimut_slot = slot == "dat_s" or slot:find("_p") elseif base.gender == "f" then is_unimut_slot = slot == "acc_s" or slot == "dat_s" or slot == "dat_p" or slot == "gen_p" else interr( "'unimut' shouldn't be specified with neuter nouns; don't know what slots would be affected; neuter pluralia tantum nouns using 'unimut' should have synthesized a singular without i-mutation") end if is_unimut_slot then mut_not_in_effect = "i" mut_footnotes = props.unimut_footnotes elseif props.unimut then mut_in_effect = "i" end elseif props.imut ~= nil then if ending_in_i then if props.imut then mut_in_effect = "i" mut_footnotes = props.imut_footnotes elseif props.imut == false then mut_not_in_effect = "i" mut_footnotes = props.imut_footnotes end end end if props.unumut ~= nil then local is_unumut_slot if props.unumut == "unumut" or props.unumut == "-unumut" then is_unumut_slot = ending_in_a or ending_in_i elseif base.gender == "m" then is_unumut_slot = slot == "gen_s" or slot == "gen_p" elseif base.gender == "f" then is_unumut_slot = slot == "nom_p" or slot == "acc_p" or slot == "gen_p" else interr( "'unumut' and variants shouldn't be specified with neuter nouns; don't know what slots would be affected; neuter pluralia tantum nouns using 'unumut'and variants should have synthesized a singular without u-mutation") end if not mut_in_effect and not mut_not_in_effect then -- Do nothing if mut_in_effect or mut_not_in_effect because i-mut takes precedence over u-mut; -- FIXME: I hope this is correct in all cases. if is_unumut_slot then mut_not_in_effect = "u" mut_footnotes = props.unumut_footnotes elseif props.unumut then mut_in_effect = "u" end end end if ending_in_u and not mut_in_effect then mut_in_effect = "u" -- umut and uUmut footnotes are incorporated into the appropriate umut_* stems end end local ending_was_asterisk = ending == "*" -- Now compute the appropriate stem to which the ending and clitic are added. `prefix` is either an empty -- string or "pl_" and selects the set of stems to consider when computing the stem in effect. See the -- comment above for `pl_stem`. local function compute_stem_in_effect(prefix) local stem_in_effect if mut_in_effect == "i" then -- NOTE: It appears that imut and defcon never co-occur; otherwise we'd need to flesh out the set of -- stems to include i-mutation versions of defcon stems, similar to what we do for u-mutation. if is_vowel_ending then if not props[prefix .. "imut_vstem"] then interr(("i-mutation in effect and ending begins with a vowel but '.%simut_vstem' not defined") : format(prefix)) end stem_in_effect = props[prefix .. "imut_vstem"] else if not props[prefix .. "imut_nonvstem"] then interr(("i-mutation in effect and ending does not begin with a vowel but '.%simut_nonvstem' not defined") : format(prefix)) end stem_in_effect = props[prefix .. "imut_nonvstem"] end else -- Careful with the following logic; it is written carefully and should not be changed without a -- thorough understanding of its functioning. local has_umut = mut_in_effect == "u" -- First, if the ending is null (or "*", which eventually turns into a null ending; see below), and -- we have a vowel-initial definite-article clitic, use the special 'defcon' stem if available. if (ending == "" or ending == "*") and is_vowel_clitic then stem_in_effect = has_umut and props[prefix .. "umut_null_defvstem"] or props[prefix .. "null_defvstem"] end -- If the stem is still unset, then use the vowel or non-vowel stem if available. When u-mutation is -- active, we first check for the u-mutated version of the vowel or non-vowel stem before falling -- back to the regular vowel or non-vowel stem. Note that an expression like `has_umut and -- props[prefix .. "umut_vstem"] or props[prefix .. "vstem"]` here is NOT equivalent to an if-else -- or ternary operator expression because if `has_umut` is true and `umut_vstem` is missing, it will -- still fall back to `vstem` (which is what we want). if not stem_in_effect then if is_vowel_ending then stem_in_effect = has_umut and props[prefix .. "umut_vstem"] or props[prefix .. "vstem"] else stem_in_effect = has_umut and props[prefix .. "umut_nonvstem"] or props[prefix .. "nonvstem"] end end -- Finally, fall back to the basic stem, which is always defined. stem_in_effect = stem_in_effect or props[prefix .. "stem"] end -- If the ending is "*", it means to use the lemma as the form directly (before adding any definite -- clitic) rather than try to construct the form from a stem and ending. We need to do this for the -- lemma slot and especially for the nominative singular, because we don't have the nominative singular -- ending available and it may vary (e.g. it may be -ur, -l, -n, -a, etc. especially in the masculine). -- Not trying to construct the form from stem + ending also avoids complications from the nominative -- singular in -ur, which exceptionally does not trigger u-mutation. However, when 'defcon' is active -- and we're processing a definite form beginning with a vowel (i.e. is_vowel_clitic is set), we can't -- do this, because the form to which the clitic is added is not the lemma but the contracted version. -- As it happens, this works out because in all situations where 'defcon' is active, the nominative -- singular has a null ending. (If this weren't the case, we'd have to change all the declension -- functions to pass in the nominative singular ending in addition to other endings.) An example where -- 'defcon' is active is neuter [[mastur]] "mast" with definite nominative singular [[mastrið]]; here, -- using the lemma would incorrectly produce #[[masturið]]. -- Finally, however, if there is a footnote associated with the computed stem in effect, we need to -- preserve it. if ending == "*" then if not is_vowel_clitic or not props.defcon or props.defcon.form ~= "defcon" then local stem_in_effect_footnotes if type(stem_in_effect) == "table" then stem_in_effect_footnotes = stem_in_effect.footnotes end stem_in_effect = iut.combine_form_and_footnotes(base.actual_lemma, stem_in_effect_footnotes) end -- See comment above. When 'defcon' is not in effect, we changed the stem to be the lemma and -- want to use a null ending; otherwise, the ending is always null anyway, so it's safe to set -- it thus. ending = "" end return stem_in_effect end local stem_in_effect = props.pl_stem and slot:find("_p$") and compute_stem_in_effect("pl_") or compute_stem_in_effect("") local infix, infix_footnotes -- Compute the infix (j, v or nothing) that goes between the stem and ending. if not ending_override and is_vowel_ending then if props.vinfix and props.jinfix then interr("Can't have specifications for both '.vinfix' and '.jinfix'; should have been caught above") end if props.vinfix then infix = props.vinfix infix_footnotes = props.vinfix_footnotes elseif props.jinfix and not ending_in_i then infix = props.jinfix infix_footnotes = props.jinfix_footnotes end end -- If base-level footnotes specified, they go before any stem footnotes, so we need to extract any footnotes -- from the stem in effect and insert the base-level footnotes before. In general, we want the footnotes to -- be in the order [base.footnotes, stem.footnotes, mut_footnotes, infix_footnotes, ending.footnotes, -- clitic.footnotes]. if base.footnotes then local stem_in_effect_footnotes if type(stem_in_effect) == "table" then stem_in_effect_footnotes = stem_in_effect.footnotes stem_in_effect = stem_in_effect.form end stem_in_effect = iut.combine_form_and_footnotes(stem_in_effect, iut.combine_footnotes(base.footnotes, stem_in_effect_footnotes)) end local ending_is_full ending, ending_is_full = rsubb(ending, "^!", "") local function combine_stem_ending(stem, clitic) if stem == "?" then return "?" end local function drop_clitic_i() clitic = clitic:gsub("^i", "") end -- If we're definite-only and using the actual lemma as the stem, the clitic is already incorporated -- into the stem. if base.definiteness == "def" and ending_was_asterisk then return stem end -- % at the end of a definite ending indicates that the following i- of the clitic should drop; see -- above. if clitic_i_drops then drop_clitic_i() end local stem_with_infix = ending_is_full and "" or stem .. (infix or "") -- Drop final -j- of stem before an ending beginning with a consonant. This happens e.g. in [[kirkja]] -- "church" with genitive plural -na, producing [[kirkna]]. It does not happen with a null ending; cf. -- neuter [[emj]] "cries, shouting" and [[gremj]] "anger, irritation" (the latter not in BÍN). if stem_with_infix:find("j$") and rfind(ending, "^" .. com.cons_c) then stem_with_infix = stem_with_infix:gsub("j$", "") end local stem_with_ending -- An initial s- of the ending drops after a cluster of cons + s (including written <x>). if ending:find("^s") and (stem_with_infix:find("x$") or rfind(stem_with_infix, com.cons_c .. "s$")) then stem_with_ending = stem_with_infix .. ending:gsub("^s", "") else stem_with_ending = stem_with_infix .. ending end if clitic == "" then return stem_with_ending end if slot == "dat_p" then stem_with_ending = stem_with_ending:gsub("m$", "") end if clitic:find("^i.*[aiu]") then -- disyllabic clitics in i- -- in practice, fem acc_s -ina, dat_s -inni, gen_s -innar if rfind(stem_with_ending, com.vowel_c .. "$") then drop_clitic_i() end elseif clitic:find("^i") then -- monosyllabic clitics in i- local ending_for_clitic_dropping = ending_was_asterisk and base.lemma_ending or ending if ending_for_clitic_dropping:find("[aiu]$") then drop_clitic_i() end end return stem_with_ending .. clitic end local combined_footnotes = iut.combine_footnotes( iut.combine_footnotes(mut_footnotes, infix_footnotes), iut.combine_footnotes(ending_footnotes, clitic_footnotes) ) local clitic_with_notes = iut.combine_form_and_footnotes(clitic, combined_footnotes) if not stem_in_effect then interr("stem_in_effect is nil") end iut.add_forms(base.forms, slot_prefix .. slot, stem_in_effect, clitic_with_notes, combine_stem_ending) end end end -- Add the definite and indefinite variants of a slot by combining the appropriate stem in `props` with (optionally) an -- infix in `props` and the endings in `endings`, tacking on the definite article clitic in the definite slot variant. -- This calls the underlying function add_slotval() twice, once for indefinite forms and once for definite forms, and is -- normally called by add_decl() or similar function to add an entire declension. `endings` can be nil (no endings are -- added), a single string, a list of strings, a list of form objects (i.e. in general list form), or a table containing -- fields `indef` and `def` (each of which can be any of the previous formats) to add separate sets of endings for the -- indefinite and definite slot variants. If any of the formats for `endings` is supplied other than the separate -- indefinite/definite table, the supplied set of endings is used for both indefinite and definite slot variants. -- `ending_override` and `endings_are_full` are as in add_slotval(). local function add(base, slot, props, endings, ending_override, endings_are_full) if not endings then return end local indef_endings, def_endings if type(endings) == "table" and (endings.indef or endings.def) then indef_endings = endings.indef def_endings = endings.def else indef_endings = endings def_endings = endings end if indef_endings and base.definiteness ~= "def" then add_slotval(base, "ind_", slot, props, indef_endings, nil, ending_override, endings_are_full) end if def_endings and (base.definiteness ~= "indef" and base.definiteness ~= "none") then local clitic = clitic_articles[base.gender] if not clitic then error(("Internal error: Unrecognized value for base.gender: %s"):format(dump(base.gender))) end clitic = clitic[slot] if not clitic then error(("Internal error: Unrecognized value for `slot` in add(): %s"):format(dump(slot))) end add_slotval(base, "def_", slot, props, def_endings, clitic, ending_override, endings_are_full) end end -- Generate the accusative plural ending from the nominative plural. For feminines and neuters, both are the same. -- For masculines, drop the -r except in -ur. local function acc_p_from_nom_p(base, nom_p) if base.gender == "f" or base.gender == "n" then return nom_p end if not nom_p then return nom_p -- this is correct as `nom_p` could be nil or false and we want to return the same thing end local function form_masc_acc_p(ending) -- Form the masculine accusative by dropping -r unless the form ends in -ur, which is kept. If the ending is *, -- we substitute the entire actual lemma. In that case, if the lemma is definite-only, we have to strip off -- the nominative plural clitic -nir before generating the accusative. We don't add the clitic -na because it -- will be added in add_slotval(). if ending == "*" then ending = "!" .. base.actual_lemma end if base.definiteness == "def" and ending:find("^!") then ending = ending:match("^(.*)nir$") if not ending then error(("Masculine plural definite-only lemma '%s' does not end in expected clitic '-nir'; " .. "don't know how to compute the corresponding accusative plural"):format(base.actual_lemma)) end end -- If the ending is full (begins with !), check the whole thing for -ur at the end. if ending:find("^%^*ur$") or ending:find("^!.*[^Aa]ur$") then -- as-is else ending = ending:gsub("r$", "") end return ending end if type(nom_p) == "string" then return form_masc_acc_p(nom_p) end local acc_p = {} for _, ending in ipairs(nom_p) do if type(ending) == "string" then table.insert(acc_p, form_masc_acc_p(ending)) else table.insert(acc_p, { form = form_masc_acc_p(ending.form), footnotes = ending.footnotes }) end end return acc_p end local function process_one_slot_override(base, slot, spec) -- Call skip_slot() based on the declined number and definiteness; if the actual number is different, we correct -- this in decline_noun() at the end. if skip_slot(base.number, base.definiteness, slot) then error(("Override specified for invalid slot '%s' due to '%s' number restriction and/or '%s' definiteness restriction") :format( slot, base.number, base.definiteness)) end local defslot = slot:find("^def_") if defslot then base.forms[slot] = nil else if spec.indef ~= false then base.forms["ind_" .. slot] = nil end if spec.def ~= false then base.forms["def_" .. slot] = nil end end if defslot then local slot_prefix -- Don't call add(), like below, because it adds both indefinite and definite variants, including definite -- clitics in the latter. Instead, directly call add_slotval(). But we need to separate the slot into slot -- prefix "def_" and the remainder because add_slotval() expects slots to be missing the prefix when -- checking which stem to use (which may depend on the slot). slot_prefix, slot = slot:match("^(def_)(.*)$") for _, props in ipairs(base.prop_sets) do add_slotval(base, slot_prefix, slot, props, spec.def, nil, "ending override") end else local endings if spec.indef ~= nil and spec.def ~= nil then -- This could include `false` as the value of either `spec.indef` or `spec.def` to not touch those slots. -- Note that specifying something like 'dat/i' is allowed and will only override the definite slot, but -- is different from a definite-slot override 'defdatinum' because the latter includes the clitic in it. endings = { indef = spec.indef, def = spec.def, } elseif not spec.indef then error(("Internal error: Unless both `spec.indef` and `spec.def` have non-nil values (i.e. the user included a slash in the override, `spec.indef` must be defined: %s") :dump(spec)) elseif slot == "acc_p" then -- As a special case, don't carry over literary acc_p ending -u to the definite. local def_endings = {} for _, ending in ipairs(spec.indef) do -- If the ending is full (begins with !), check the whole thing for -u at the end. if not ending.form:find("^%^*u$") and not ending.form:find("^!.*[^Aa]u$") then table.insert(def_endings, ending) end end endings = { indef = spec.indef, def = def_endings, } else endings = spec.indef end for _, props in ipairs(base.prop_sets) do add(base, slot, props, endings, "ending override") end end end local function process_slot_overrides(base) if base.gens then process_one_slot_override(base, "gen_s", base.gens) end if base.pls then local spec = base.pls process_one_slot_override(base, "nom_p", spec) local acc_p_spec = { indef = acc_p_from_nom_p(base, spec.indef), def = acc_p_from_nom_p(base, spec.def), } process_one_slot_override(base, "acc_p", acc_p_spec) end for slot, spec in pairs(base.overrides) do process_one_slot_override(base, slot, spec) end end -- Generate the full declension for the term given the endings for each slot. acc_p, dat_p and gen_p can be omitted and -- will be defaulted: dat_p defaults to "um", gen_p defaults to "a", and acc_p defaults to the nom_p except for masculines -- not in -ur, where the -r is dropped. Use `false` as the value of an ending to disable generating any value for that -- slot. local function add_decl_with_nom_sg(base, props, nom_s, acc_s, dat_s, gen_s, nom_p, acc_p, dat_p, gen_p) add(base, "nom_s", props, nom_s) add(base, "acc_s", props, acc_s) add(base, "dat_s", props, dat_s) add(base, "gen_s", props, gen_s) if base.number == "pl" then -- If this is a plurale tantum noun and we're processing the nominative plural, use the user-specified lemma -- rather than generating the plural from the synthesized singular, which may not match the specified lemma. -- This is both because we don't set a plural override to specify what the plural should look like and because -- of exceptional cases like [[dyr]], which is plural-only and uses 'decllemma:dyrir'. nom_p = "*" end add(base, "nom_p", props, nom_p) -- Generate defaults for acc_p, dat_p, gen_p if nil was specified; but be careful not to do so for false, which -- means to generate no form. if acc_p == nil then acc_p = acc_p_from_nom_p(base, nom_p) end if dat_p == nil then dat_p = "um" end if gen_p == nil then gen_p = "a" end add(base, "acc_p", props, acc_p) add(base, "dat_p", props, dat_p) add(base, "gen_p", props, gen_p) end -- Generate the full declension for the term given the endings for each slot except the nom_s. This is like -- add_decl_with_nom_sg() but takes the nom sg directly from the lemma instead of trying to reconstruct it from a stem, -- which is more correct in the vast majority of circumstances. The * below is a signal to the underlying add() function -- to use the actual lemma (not any stem, and not the value of 'decllemma:' if given) for the nom sg. Note that add() is -- smart enough to ignore this for the definite nom sg when the 'defcon' indicator is given, because in that case the stem -- for the def nom sg is contracted compared with the lemma. (Specifically, it uses the correct contracted stem and a null -- ending; AFAIK all cases of 'defcon' occur with lemmas with a null ending in the nom sg.) local function add_decl(base, props, acc_s, dat_s, gen_s, nom_p, acc_p, dat_p, gen_p) add_decl_with_nom_sg(base, props, "*", acc_s, dat_s, gen_s, nom_p, acc_p, dat_p, gen_p) end local function add_sg_decl(base, props, acc_s, dat_s, gen_s) add_decl(base, props, acc_s, dat_s, gen_s, false, false, false, false) end local function add_pl_only_decl(base, props, acc_p, dat_p, gen_p) add_decl(base, props, false, false, false, "*", acc_p, dat_p, gen_p) end -- Table mapping declension types to functions to decline the noun. The function takes two arguments, `base` and -- `props`; the latter specifies the computed stems (vowel vs. non-vowel, singular vs. plural) and whether the noun -- is reducible and/or has vowel alternations in the stem. Most of the specifics of determining which stem to use -- and how to modify it for the given ending are handled in add_decl(); the declension functions just need to generate -- the appropriate endings. local decls = {} decls["indecl"] = function(base, props) add_decl(base, props, "", "", "", "", "", "", "") end decls["decl?"] = function(base, props) add_decl(base, props, "?", "?", "?", "?", "?", "?", "?") end decls["m"] = function(base, props) -- The default dative singular is computed below in determine_default_masc_dat_sg(). local dat = props.default_dat_sg add_decl(base, props, "", dat, "s", "ar") end decls["m-ir"] = function(base, props) add_decl(base, props, "i", "i", "is", "ar") end decls["m-skapur"] = function(base, props) -- Nouns in -skapur; default gen is -ar, default dat is -/-, default num is sg. add_decl(base, props, "", "", "ar", "ar") end decls["m-naður"] = function(base, props) -- Nouns in -naður; default gen is -ar, default dat is dati/i:-, default nom pl is -ir, default num is sg, -- default u-mutation is uUmut. add_decl(base, props, "", { indef = "i", def = { "i", "" } }, "ar", "ir") end decls["m-kell"] = function(base, props) -- Proper nouns in -kell; [[Þorkell]], [[Grímkell]], etc. local alt_dat_s = base.stem:gsub("kel$", "katli") add_decl(base, props, "", { "i", { form = "!" .. alt_dat_s, footnotes = { "[archaic]" } } }, "s", false, false, false, false) end decls["m-ó"] = function(base, props) -- abbreviations of school names generally have null genitive: [[Kennó]] from [[Kennaraskóla]] "Teachers' College"), -- [[Astró]], [[Borgó]] (from [[Borgarholtsskóli]]), [[Bríó]], [[Foldó]] (from [[Foldaskóli]]), [[Hafró]] (from -- [[Hafrannsóknastofnun]] "Marine Research Institute" (of Norway), [[Hagó]] (from [[Hagaskóli]]), [[Húsó]], -- [[Kvennó]] (from [[Kvennaskóli]]), [[Meló]] (from [[Melaskóli]]), [[Menntó]] (from [[Menntaskóli]]), [[Tónó]] -- (from [[Tónlistarskóli]]), [[Való]] (from [[Valhúsaskóli]]), [[Versló]]/[[Verzló]] (from -- [[Verslunarskóli Íslands|Iceland Business School]]); but these are completely outweighed by male given names, -- nicknames and historical names of men in -ó (e.g. [[Bó]], [[Bóbó]], [[Brúnó]], [[Dittó]], [[Filpó]], [[Galíleó]], -- [[Jagó]], [[Kató]], [[Kristó]], [[Leó]], [[Leónardó]], [[Markó]], etc.) as well as common nouns in -ó (e.g. -- [[bóleró]] "bolero", [[evró]] "Euro (dated)", [[faraó]] "pharaoh", [[kanó]] "canoe", [[kímonó]] "kimono", -- [[mambó]] "mambo", [[pesó]] "peso", [[pikkóló]] "piccolo", [[róló]] "playground", [[sleikjó]] "lollipop", -- etc.) add_decl(base, props, "", "", "s", "ar") end decls["m-rstem"] = function(base, props) local imut = "^" add_decl(base, props, "ur", "ur", { "ur", { form = "urs", footnotes = { "[proscribed]" } } }, imut .. "ur", nil, imut .. "rum", imut .. "ra") end decls["m-ndi"] = function(base, props) -- Words in -ndi, mostly derived from present participles and mostly in [[andi]]; but cf. [[bóndi]], [[frændi]], -- and [[fjandi]] with two plurals with different meanings. local imut if props.stem:find("ænd$") then imut = "" else imut = "^" end add_decl(base, props, "a", "a", "a", imut .. "ur", nil, { imut .. "um", { form = "um", footnotes = "[rare/obsolete]" } }, { imut .. "a", { form = "a", footnotes = "[rare/obsolete]" } }) end decls["m-weak"] = function(base, props) -- Words in -i like [[tími]] "time, hour"; also words in -a e.g. [[herra]] "gentleman; sir, Mr. (term of address)", -- [[séra]]/[[síra]] "reverend" add_decl(base, props, "a", "a", "a", "ar") end decls["f"] = function(base, props) -- Normal strong feminine nouns; default to genitive -ar, plural -ir. add_decl(base, props, "", "", "ar", "ir") end decls["f-ung"] = function(base, props) -- Strong feminine nouns in -ung, e.g. [[nýjung]] "newness, novelty; piece of news", [[nauðung]] -- "constraint, compulsion". Most such nouns are singular-only, e.g. [[djörfung]] "boldness, daring", [[launung]] -- "secrecy". Occasional nouns need overrides, e.g. [[sundrung]] "scattering; dissension, division, disunity" with -- acc/dat sg either - or -u (but only - in the definite acc/dat sg). add_decl(base, props, "", "", "ar", "ar") end decls["f-ing"] = function(base, props) -- Strong feminine nouns in -ing, e.g. [[kerling]] "old woman", [[eining]] "unity; unit". Singular-only: e.g. -- [[málning] "paint", [[menning]] "culture", [[örvænting]] "despair". add_decl(base, props, "u", "u", "ar", "ar") end decls["f-ur"] = function(base, props) add_decl(base, props, "i", "i", "ar", "ir") end decls["f-i"] = function(base, props) add_decl(base, props, "i", "i", "i", "ir") end decls["f-long-vowel"] = function(base, props) -- nouns in -á, e.g. [[á]] "river", [[gjá]] "gorge, canyon", [[skuggská]] "mirror", [[slá]] "door bolt"; -- nouns in -ó, e.g. [[fló]] "flea", [[kónguló]] "spider", [[kló]] "claw"; -- nouns in -ú, e.g. [[frú]] "married woman", [[trú]] "faith, belief". -- Each is slightly different. local gen, nompl if props.stem:find("á$") then gen = "r" nompl = "r" elseif props.stem:find("ó$") then gen = "ar" nompl = "^r" elseif props.stem:find("ú$") then gen = "ar" nompl = "r" else error(("Unrecognized stem '%s' for long-vowel feminine; should end in -á, -ó or -ú")) end add_decl(base, props, "", "", gen, nompl, nompl, "m", { indef = "a", def = "" }) end decls["f-long-umlaut-vowel-r"] = function(base, props) -- nouns in long umlauted vowel + -r: [[kýr]] "cow", [[sýr]] "sow (archaic)", [[ær]] and compounds. add_decl(base, props, "", "", "^r", "^r", "^r", "m", { indef = "a", def = "" }) end decls["f-acc-dat-i"] = function(base, props) -- Some proper female names with -i in the acc and dat sg add_decl(base, props, "i", "i", "ar", "ar") end decls["f-rstem"] = function(base, props) local imut if props.stem:find("syst$") then imut = "" else imut = "^" end local sg_ending = { "ur", { form = "ir", footnotes = { "[proscribed]" } } } add_decl(base, props, sg_ending, sg_ending, sg_ending, imut .. "ur", nil, imut .. "rum", imut .. "ra") end decls["f-weak"] = function(base, props) add_decl(base, props, "u", "u", "u", "ur") end decls["n"] = function(base, props) -- Normal (strong) neuter nouns. add_decl(base, props, "", "i", "s", "^^") end decls["n-já"] = function(base, props) -- [[tré]] "tree; wood"; [[hné]]/[[kné]] "knee"; [[fé]] "sheep; cattle; money"; the stem has previously been set -- to not include final -é; fé has genitive fjár while the others have genitive in -és. local gen = props.stem:find("f$") and "jár" or "és" add_decl_with_nom_sg(base, props, "é%", "é%", "é", gen, "é%", "é%", "jám", { indef = "jáa", def = "já" }) end decls["n-i"] = function(base, props) -- Neuter nouns in -i, e.g. [[kvæði]] "poem, song". Nouns in -ki and -gi e.g. [[ríki]] "state, kingdom" and [[engi]] -- "meadow" have j-insertion by default, which is set elsewhere. add_decl(base, props, "i", "i", "is", "i") end decls["n-weak"] = function(base, props) -- "Weak" neuter nouns in -a, e.g. [[auga]] "eye", [[hjarta]] "heart". U-mutation occurs in the nom/acc/dat pl but -- doesn't need to be indicated explicitly because the ending begins with u-. add_decl(base, props, "a", "a", "a", "u") end local function reconstruct_control_spec(control_specs) local parts = {} local function ins(txt) table.insert(parts, txt) end for i, spec in ipairs(control_specs) do if i > 1 then ins(",") end ins(spec.form) if spec.footnotes then for _, footnote in ipairs(spec.footnotes) do ins(footnote) -- already has brackets around it end end end return table.concat(parts) end decls["adj"] = function(base, _props) -- This maps from a slot name constructed from the individual state, case, gender and number properties to the -- actual syncretic slot name used in [[Module:is-adjective]]. local slot_to_syncretic_slot_mapping = { str_nom_m_s = "str_nom_m", str_nom_f_s = "str_nom_f", str_nom_n_s = "str_nom_n", str_acc_m_s = "str_acc_m", str_acc_f_s = "str_acc_f", str_acc_n_s = "str_nom_n", str_dat_m_s = "str_dat_m", str_dat_f_s = "str_dat_f", str_dat_n_s = "str_dat_n", str_gen_m_s = "str_gen_m", str_gen_f_s = "str_gen_f", str_gen_n_s = "str_gen_n", str_nom_m_p = "str_nom_mp", str_nom_f_p = "str_nom_fp", str_nom_n_p = "str_nom_np", str_acc_m_p = "str_acc_mp", str_acc_f_p = "str_nom_fp", str_acc_n_p = "str_nom_np", str_gen_m_p = "str_gen_p", str_gen_f_p = "str_gen_p", str_gen_n_p = "str_gen_p", str_dat_m_p = "str_dat_p", str_dat_f_p = "str_dat_p", str_dat_n_p = "str_dat_p", wk_nom_m_s = "wk_nom_m", wk_nom_f_s = "wk_nom_f", wk_nom_n_s = "wk_n", wk_acc_m_s = "wk_obl_m", wk_acc_f_s = "wk_obl_f", wk_acc_n_s = "wk_n", wk_dat_m_s = "wk_obl_m", wk_dat_f_s = "wk_obl_f", wk_dat_n_s = "wk_n", wk_gen_m_s = "wk_obl_m", wk_gen_f_s = "wk_obl_f", wk_gen_n_s = "wk_n", wk_nom_m_p = "wk_p", wk_nom_f_p = "wk_p", wk_nom_n_p = "wk_p", wk_acc_m_p = "wk_p", wk_acc_f_p = "wk_p", wk_acc_n_p = "wk_p", wk_gen_m_p = "wk_p", wk_gen_f_p = "wk_p", wk_gen_n_p = "wk_p", wk_dat_m_p = "wk_p", wk_dat_f_p = "wk_p", wk_dat_n_p = "wk_p", } local props = {} local function ins(prop) table.insert(props, prop) end for _, spectype in ipairs(m_is_adjective.control_specs) do if base[spectype] then ins(reconstruct_control_spec(base[spectype])) end end -- If a specific reverse u-mutation type was specified and no u-mutation was given, convert the reverse -- u-mutation into a regular u-mutation by chopping off the "un" at the beginning. if base.adj_unumut and not base.umut then ins(base.adj_unumut:sub(3)) end for k, _ in pairs(base.props) do if m_is_adjective.boolean_property_set[k] then ins(k) end end if not base.props.builtin then ins(base.props.iscomp and "-pos" or "-comp") end if base.stem == "#" or base.stem == "##" then ins(base.stem) elseif base.stem then ins("stem:" .. base.stem) end for _, stem in ipairs(m_is_adjective.overridable_stems) do if stem ~= "stem" and base[stem] then ins(("%s:%s"):format(stem, base.stem)) end end local propspec = table.concat(props, ".") if propspec ~= "" then propspec = "<" .. propspec .. ">" end local argspec = base.lemma .. propspec local adj_alternant_multiword_spec = m_is_adjective.do_generate_forms({ argspec }, argspec, "is-ndecl") local function copy(from_slot, to_slot, do_clone) -- We want to avoid sharing form objects (although sharing footnotes is OK, but we don't avoid cloning them -- here) so we can later side-effect form objects as needed. `do_clone` is set to avoid such sharing, -- specifically when the weak form of the adjective is used for both definite and indefinite slots. local source = adj_alternant_multiword_spec.forms[from_slot] if do_clone then source = m_table.deepCopy(source) end base.forms[to_slot] = source end local function copy_gender_number_forms(gender, number) local state = base.adj_is_weak and "wk" or "str" local degree_pref = base.props.iscomp and "comp_" or "" for _, case in ipairs(cases) do local individual_slot = state .. "_" .. case .. "_" .. gender .. "_" .. number local wk_individual_slot = "wk_" .. case .. "_" .. gender .. "_" .. number local syncretic_slot = slot_to_syncretic_slot_mapping[individual_slot] local wk_syncretic_slot = slot_to_syncretic_slot_mapping[wk_individual_slot] if not syncretic_slot then error(("Internal error: Constructed bad individual slot '%s' with no entry in syncretic slot mapping"): format(individual_slot)) end syncretic_slot = degree_pref .. syncretic_slot if not wk_syncretic_slot then error(("Internal error: Constructed bad weak individual slot '%s' with no entry in syncretic slot mapping") : format(wk_individual_slot)) end wk_syncretic_slot = degree_pref .. wk_syncretic_slot copy(syncretic_slot, "ind_" .. case .. "_" .. number) copy(wk_syncretic_slot, "def_" .. case .. "_" .. number, syncretic_slot == wk_syncretic_slot) end end if base.number ~= "pl" then copy_gender_number_forms(base.gender, "s") end if base.number ~= "sg" then copy_gender_number_forms(base.gender, "p") end end local function set_builtin_defaults(base) if base.gender or base.number or base.definiteness then error("Can't specify gender, number or definiteness for built-in terms") end local function builtin_props() -- Return values are GENDER, NUMBER if base.lemma == "ég" or base.lemma == "þú" then return "none", "sg" elseif base.lemma == "við" or base.lemma == "þið" then return "none", "pl" elseif base.lemma == "hann" then return "m", "sg" elseif base.lemma == "hún" then return "f", "sg" elseif base.lemma == "það" then return "n", "sg" elseif base.lemma == "þeir" then return "m", "pl" elseif base.lemma == "þær" then return "f", "pl" elseif base.lemma == "þau" then return "n", "pl" elseif base.lemma == "sig" then return "none", "none" else error(("Unrecognized pronoun '%s'"):format(base.lemma)) end end local gender, number = builtin_props() base.gender = gender base.actual_gender = gender base.number = number base.actual_number = number base.definiteness = "none" end local function determine_builtin_props(base) base.prop_sets[1].stem = { form = "" } base.decl = "builtin" end decls["builtin"] = function(base, props) if base.lemma == "ég" then add_sg_decl(base, props, "mig", "mér", "mín") elseif base.lemma == "þú" then add_sg_decl(base, props, "þig", "þér", "þín") elseif base.lemma == "hann" then add_sg_decl(base, props, "hann", "honum", "hans") elseif base.lemma == "hún" then add_sg_decl(base, props, "hana", "henni", "hennar") elseif base.lemma == "það" then add_sg_decl(base, props, "það", "því", "þess") elseif base.lemma == "við" then add_pl_only_decl(base, props, "okkur", "okkur", "okkar") elseif base.lemma == "þið" then add_pl_only_decl(base, props, "ykkur", "ykkur", "ykkar") elseif base.lemma == "þeir" then add_pl_only_decl(base, props, "þá", "þeim", "þeirra") elseif base.lemma == "þær" then add_pl_only_decl(base, props, "þær", "þeim", "þeirra") elseif base.lemma == "þau" then add_pl_only_decl(base, props, "þau", "þeim", "þeirra") elseif base.lemma == "sig" then -- Underlyingly we handle [[sig]]'s slots as singular. add_decl_with_nom_sg(base, props, false, "*", "sér", "sín", false, false, false, false) else error(("Internal error: Unrecognized pronoun lemma '%s'"):format(base.lemma)) end end -- Return the lemmas for this term. The return value is a list of {form = FORM, footnotes = FOOTNOTES}. -- If `linked_variant` is given, return the linked variants (with embedded links if specified that way by the user), -- otherwies return variants with any embedded links removed. If `remove_footnotes` is given, remove any -- footnotes attached to the lemmas. function export.get_lemmas(alternant_multiword_spec, linked_variant, remove_footnotes) local slots_to_fetch = potential_lemma_slots local linked_suf = linked_variant and "_linked" or "" for _, slot in ipairs(slots_to_fetch) do if alternant_multiword_spec.forms[slot .. linked_suf] then local lemmas = alternant_multiword_spec.forms[slot .. linked_suf] if remove_footnotes then local lemmas_no_footnotes = {} for _, lemma in ipairs(lemmas) do table.insert(lemmas_no_footnotes, { form = lemma.form }) end return lemmas_no_footnotes else return lemmas end end end return {} end local function handle_derived_slots_and_overrides(base) -- Process slot overrides: First slots specified after the gender, then individual slot overrides specified as -- separate indicators. process_slot_overrides(base) -- Compute linked versions of potential lemma slots, for use in {{is-noun}}. We substitute the original lemma -- (before removing links) for forms that are the same as the lemma, if the original lemma has links. for _, slot in ipairs(potential_lemma_slots) do iut.insert_forms(base.forms, slot .. "_linked", iut.map_forms(base.forms[slot], function(form) if form == base.orig_lemma_no_links then if base.orig_lemma:find("%[%[") then return base.orig_lemma elseif not base.is_multiword then return form elseif not base.props.linkasis and (base.lemma ~= base.orig_lemma_no_links or base.link_lowercase) then local lemma_for_linking = base.lemma if base.link_lowercase then local init, rest = rmatch(lemma_for_linking, "^(.)(.*)$") lemma_for_linking = ulower(init) .. rest end return ("[[%s|%s]]"):format(lemma_for_linking, base.orig_lemma_no_links) else return ("[[%s]]"):format(form) end else return form end end)) end end -- Process specs given by the user using 'addnote[SLOTSPEC][FOOTNOTE][FOOTNOTE][...]'. local function process_addnote_specs(base) for _, spec in ipairs(base.addnote_specs) do for _, slot_spec in ipairs(spec.slot_specs) do slot_spec = "^" .. slot_spec .. "$" for slot, forms in pairs(base.forms) do if rfind(slot, slot_spec) then -- To save on memory, side-effect the existing forms. for _, form in ipairs(forms) do form.footnotes = iut.combine_footnotes(form.footnotes, spec.footnotes) end end end end end end local function is_regular_noun(base) return not base.adjspec and not base.props.builtin end local function process_declnumber(base) base.actual_number = base.number if base.declnumber then if base.declnumber == "sg" or base.declnumber == "pl" then base.number = base.declnumber else error(("Unrecognized value '%s' for 'declnumber', should be 'sg' or 'pl'"):format(base.declnumber)) end end end -- Map `fn` over an override spec (either `gens`, `pls` or one of the overrides in `overrides`). `fn` is passed one -- item (the form object of the override), which it can mutate if needed. If it ever returns non-nil, mapping stops -- and that value is returned as the return value of `map_override`; otherwise mapping runs to completion and nil is -- returned. local function map_override(override, fn) if not override then return nil end local function map_one_list(list) if not list then return nil end for _, formobj in ipairs(list) do local retval = fn(formobj) if retval ~= nil then return retval end end return nil end local retval = map_one_list(override.indef) if retval ~= nil then return retval end return map_one_list(override.def) end -- Map `fn` over all override specs in `base` (`gens`, `pls` and the overrides in `overrides`). `fn` is passed one -- item (the form object of the override), which it can mutate if needed. If it ever returns non-nil, mapping stops -- and that value is returned as the return value of `map_override`; otherwise mapping runs to completion and nil is -- returned. local function map_all_overrides(base, fn) for slot, override in pairs(base.overrides) do local retval = map_override(override, fn) if retval ~= nil then return retval end end local retval = map_override(base.gens, fn) if retval ~= nil then return retval end return map_override(base.pls, fn) end -- Like put.split_alternating_runs_and_strip_spaces(), but ensure that backslash-escaped commas and periods are not -- treated as separators. local function split_alternating_runs_with_escapes(segments, splitchar) for i, segment in ipairs(segments) do segment = rsub(segment, "\\,", SUB_ESCAPED_COMMA) segments[i] = rsub(segment, "\\%.", SUB_ESCAPED_PERIOD) end local separated_groups = put.split_alternating_runs_and_strip_spaces(segments, splitchar) for _, separated_group in ipairs(separated_groups) do for i, segment in ipairs(separated_group) do segment = rsub(segment, SUB_ESCAPED_COMMA, ",") separated_group[i] = rsub(segment, SUB_ESCAPED_PERIOD, ".") end end return separated_groups end local function fetch_footnotes(separated_group, parse_err) local footnotes for j = 2, #separated_group - 1, 2 do if separated_group[j + 1] ~= "" then parse_err("Extraneous text after bracketed footnotes: '" .. table.concat(separated_group) .. "'") end if not footnotes then footnotes = {} end table.insert(footnotes, separated_group[j]) end return footnotes end -- Fetch and parse a slot override, e.g. "ar:s" or "um:m[archaic]/um" or "i:!Þorkatli[archaic]" (where ! indicates that -- the override is the full form including the stem); that is, everything after the slot name(s). `segments` is the -- input in the form of a list where the footnotes have been separated out (see `parse_override` below); `spectype` is -- used in error messages and specifies e.g. "genitive" or "dat+gen slot override"; `allow_blank` indicates that a -- completely blank override spec is allowed (in that case, nil will be returned); `defslot`, if true, indicates that -- we're processing a definite slot override, i.e. two slash-separated specs (indefinite and definite) are not allowed -- and the return overrides will be stored into `def`; and `parse_err` is a function of one argument to throw a parse -- error. The return value is an object containing fields `indef` and/or `def`, of the format described below in the -- comment above `parse_override`. local function fetch_slot_override(segments, spectype, allow_blank, defslot, parse_err) if allow_blank and #segments == 1 and segments[1] == "" then return nil end local slash_separated_groups = put.split_alternating_runs_and_strip_spaces(segments, "/") if #slash_separated_groups > 2 then parse_err(("Can specify at most two slash-separated override groups for %s, but saw %s"):format( spectype, #slash_separated_groups)) end if slash_separated_groups[2] and defslot then parse_err(("Can't specify two slash-separated override groups for %s; the second override group is for the definite slot variant, but the slot is already definite") :format( spectype)) end local ret = {} for i, slash_separated_group in ipairs(slash_separated_groups) do local retfield = defslot and "def" or i == 1 and "indef" or "def" if #slash_separated_group == 1 and slash_separated_group[1] == "" then ret[retfield] = false else local colon_separated_groups = put.split_alternating_runs_and_strip_spaces(slash_separated_group, ":") local specs = {} for _, colon_separated_group in ipairs(colon_separated_groups) do local form = colon_separated_group[1] if form == "" then parse_err(("Use - to indicate an empty ending for %s: '%s'"):format(spectype, table.concat(segments))) elseif form == "-" then form = "" elseif form == "--" then -- missing form = "-" end local new_spec = { form = form, footnotes = fetch_footnotes(colon_separated_group, parse_err) } for _, existing_spec in ipairs(specs) do if existing_spec.form == new_spec.form then parse_err("Duplicate " .. spectype .. " spec '" .. table.concat(colon_separated_group) .. "'") end end table.insert(specs, new_spec) end ret[retfield] = specs end end return ret end --[=[ Parse a single override spec (e.g. 'dat-:i/-' or 'nompl+accpl^/' or 'defnompl+defaccpl!sumrin[when referring to summers in general]:!sumurin[when referring to a specific number of summers]') and return two values: the slot(s) the override applies to, and an object describing the override spec. The input is actually a list where the footnotes have been separated out; for example, given the third example spec above, the input will be a list {"defnompl+defaccpl!sumrin", "[when referring to summers in general]", ":!sumurin", "[when referring to a specific number of summers]", ""}. The object returned for 'dat-:i[mostly in the context of violent actions]/-' looks like this: { indef = { { form = "" }, { form = "i", footnotes = {"[mostly in the context of violent actions]"} } }, def = { { form = "" } } } The object returned for '!nompl+accpl^/' looks like this: { indef = { { form = "^" }, }, def = false } The object returned for 'defnompl+defaccpl!sumrin[when referring to summers in general]:!sumurin[when referring to a specific number of summers]' looks like this: { def = { { form = "!sumrin", footnotes = {"[when referring to summers in general]"} }, { form = "!sumurin", footnotes = {"[when referring to a specific number of summers]"} } } } ]=] local function parse_override(segments, parse_err) local part = segments[1] local slots = {} local defslot while true do local this_defslot if part:find("^def") then this_defslot = true part = usub(part, 4) else this_defslot = false end if defslot == nil then defslot = this_defslot elseif defslot ~= this_defslot then parse_err(("When multiple slot overrides are combined with +, all must be definite or indefinite: '%s'"): format(table.concat(segments))) end local case = usub(part, 1, 3) if case_set[case] then -- ok else parse_err(("Unrecognized case '%s' in override: '%s'"):format(case, table.concat(segments))) end part = usub(part, 4) local slot = defslot and "def_" or "" if part:find("^pl") then part = usub(part, 3) slot = slot .. case .. "_p" else slot = slot .. case .. "_s" end table.insert(slots, slot) if part:find("^%+") then part = usub(part, 2) else break end end segments[1] = part local retval = fetch_slot_override(segments, ("%s slot override"):format(table.concat(slots, "+")), false, defslot, parse_err) return slots, retval end local function parse_adjspec(_base, spec, parse_err) local ret = {} local origspec = spec if spec:find("^:") then ret.lemma = spec:sub(2) elseif spec:find("^/") then local from, to = spec:match("^/([^/]*)/([^/]*)$") if from then ret.subspec = { from = from, to = to } else to = spec:match("^/([^/]*)$") if to then ret.subspec = { to = to } else parse_err(("Syntax error in adjective spec 'adj%s': too many slashes"):format(origspec)) end end elseif spec ~= "" then parse_err(("Syntax error in adjective spec 'adj%s'; should be followed only by a colon + lemma or slash " .. "substitution spec, possibly preceded by ^ to indicate lowercasing"):format(origspec)) end return ret end local function parse_inside(base, inside, is_scraped_noun) local function parse_err(msg) error((is_scraped_noun and "Error processing scraped noun spec: " or "") .. msg .. ": <" .. inside .. ">") end local segments = put.parse_balanced_segment_run(inside, "[", "]") local dot_separated_groups = split_alternating_runs_with_escapes(segments, "%.") local isadj = false for i, dot_separated_group in ipairs(dot_separated_groups) do -- Parse a control spec such as "umut,uUmut[rare]" or "-unuUmut,unuUmut" or "imut". This assumes the control -- spec is contained in `dot_separated_group` (already split on brackets) and the result of parsing should go in -- `base[dest]`. `allowed_specs` is a list of the allowed control specs in this group, such as -- {"umut", "Umut", "uumut", "uUmut", "uUUmut", "u_mut"} or {"con", "-con"}. The result of parsing is a list of -- structures of the form { -- form = "FORM", -- footnotes = nil or {"FOOTNOTE", "FOOTNOTE", ...}, -- }. local function parse_control_spec(dest, allowed_specs) if base[dest] then parse_err(("Can't specify '%s'-type control spec twice; second such spec is '%s'"):format( dest, table.concat(dot_separated_group))) end base[dest] = {} local comma_separated_groups = split_alternating_runs_with_escapes(dot_separated_group, ",") for _, comma_separated_group in ipairs(comma_separated_groups) do local specobj = {} local spec = comma_separated_group[1] if not m_table.contains(allowed_specs, spec) then parse_err(("For '%s'-type control spec, saw unrecognized spec '%s'; valid values are %s"): format(dest, spec, generate_list_of_possibilities_for_err(allowed_specs))) else specobj.form = spec end specobj.footnotes = fetch_footnotes(comma_separated_group, parse_err) table.insert(base[dest], specobj) end end local part = dot_separated_group[1] while true do if i == 1 and not part:find("^adj") and not part:find("^@") and part ~= "builtin" then local comma_separated_groups = split_alternating_runs_with_escapes(dot_separated_group, ",") if #comma_separated_groups > 3 then parse_err(("At most three comma-separated specs are allowed but saw %s"):format( #comma_separated_groups)) end if comma_separated_groups[1][2] then parse_err("Footnotes not allowed on gender indicator") end base.gender = comma_separated_groups[1][1] if not base.gender:find("^[mfn]$") then parse_err(("Unrecognized gender '%s', should be 'm', 'f' or 'n'"):format(base.gender)) end if comma_separated_groups[2] then base.gens = fetch_slot_override(comma_separated_groups[2], "genitive", true, false, parse_err) end if comma_separated_groups[3] then base.pls = fetch_slot_override(comma_separated_groups[3], "nominative plural", true, false, parse_err) end break elseif part == "" then if not dot_separated_group[2] then parse_err("Blank indicator; not allowed without attached footnotes") end base.footnotes = fetch_footnotes(dot_separated_group, parse_err) break elseif part == "addnote" then local spec_and_footnotes = fetch_footnotes(dot_separated_group, parse_err) if #spec_and_footnotes < 2 then parse_err("Spec with 'addnote' should be of the form 'addnote[SLOTSPEC][FOOTNOTE][FOOTNOTE][...]'") end local slot_spec = table.remove(spec_and_footnotes, 1) local slot_spec_inside = rmatch(slot_spec, "^%[(.*)%]$") if not slot_spec_inside then parse_err("Internal error: slot_spec " .. slot_spec .. " should be surrounded with brackets") end local slot_specs = rsplit(slot_spec_inside, ",") -- FIXME: Here, [[Module:it-verb]] called strip_spaces(). Generally we don't do this. Should we? table.insert(base.addnote_specs, { slot_specs = slot_specs, footnotes = spec_and_footnotes }) break elseif ulen(part) > 3 and case_set[usub(part, 1, 3)] or ( ulen(part) > 6 and usub(part, 1, 3) == "def" and case_set[usub(part, 4, 6)]) then local slots, override = parse_override(dot_separated_group, parse_err) for _, slot in ipairs(slots) do if base.overrides[slot] then error(("Two overrides specified for slot '%s'"):format(slot)) else base.overrides[slot] = override end end break end if isadj then if m_is_adjective.parse_for_control_specs(part, parse_control_spec) then break end else if part:find("^[Uu]+_?mut") then parse_control_spec("umut", com.umut_types) break elseif not part:find("^imutval") and part:find("^%-?imut") then parse_control_spec("imut", { "imut", "-imut" }) break elseif part:find("^%-?un[uU]+_?mut") then local unumut_types_and_negated = {} for _, typ in ipairs(com.unumut_types) do table.insert(unumut_types_and_negated, typ) table.insert(unumut_types_and_negated, "-" .. typ) end parse_control_spec("unumut", unumut_types_and_negated) break elseif not part:find("^unimutval") and part:find("^%-?unimut") then parse_control_spec("unimut", { "unimut", "-unimut" }) break elseif part:find("^%-?con") then parse_control_spec("con", { "con", "-con" }) break elseif part:find("^%-?defcon") then parse_control_spec("defcon", { "defcon", "-defcon" }) break elseif not part:find("^já") and part:find("^%-?j") then -- don't trip over .já indicator parse_control_spec("j", { "j", "-j" }) break elseif not part:find("^vstem") and part:find("^%-?v") then parse_control_spec("v", { "v", "-v" }) break end end if #dot_separated_group > 1 then parse_err( ("Footnotes only allowed with slot overrides, negatable indicators and by themselves: '%s'"): format(table.concat(dot_separated_group))) elseif part:find("^adj") then if i > 1 then parse_err("Adjective spec must be the first indicator") end if base.adjspec then parse_err("Can't specify two adjective specs") end isadj = true base.adjspec = parse_adjspec(base, part:sub(4), parse_err) break elseif part:find("^[mfn]$") then if base.gender then parse_err("Can't specify gender twice") end base.gender = part break elseif not isadj and (part:find("^decllemma%s*:") or part:find("^declgender%s*:") or part:find("^declnumber%s*:")) then local field, value = part:match("^(decl[a-z]+)%s*:%s*(.+)$") if not value then parse_err(("Syntax error in decllemma/declgender/declnumber indicator: '%s'"):format(part)) end if base[field] then parse_err(("Can't specify '%s:' twice"):format(field)) end base[field] = value break elseif part:find("^q%s*:") or part:find("header%s*:") then local field, value = part:match("^(q)%s*:%s*(.+)$") if not value then field, value = part:match("^(header)%s*:%s*(.+)$") end if not value then parse_err(("Syntax error in q/header indicator: '%s'"):format(part)) end if base[field] then parse_err(("Can't specify '%s:' twice"):format(field)) end base[field] = value break elseif not isadj and part:find("^@") then -- FIXME: Implement adjective scraping if base.scrape_spec then parse_err("Can't specify scrape directive '@...' twice") end if part:find(":") then base.scrape_is_suffix, base.scrape_spec, base.scrape_id = part:match("^@(%-?)(.-)%s*:%s*(.+)$") else base.scrape_is_suffix, base.scrape_spec = part:match("^@(%-?)(.-)$") end -- If we saw a hyphen, set `scrape_is_suffix` to true, otherwise false base.scrape_is_suffix = base.scrape_is_suffix == "-" if not base.scrape_spec or base.scrape_spec == "" then parse_err(("Syntax error in scrape directive '%s"):format(part)) end local scrape_init, scrape_rest = rmatch(base.scrape_spec, "^(.)(.*)$") local lower_scrape_init = ulower(scrape_init) if ulower(scrape_init) ~= scrape_init then base.scrape_is_uppercase = true base.scrape_spec = lower_scrape_init .. scrape_rest end break elseif part:find(":") then local spec, value = part:match("^([a-z]+)%s*:%s*(.+)$") if not spec then parse_err(("Syntax error in indicator with value, expecting alphabetic slot or stem/lemma " .. "override indicator: '%s'"):format(part)) end local stem_set = isadj and m_is_adjective.overridable_stem_set or overridable_stem_set if not stem_set[spec] then parse_err(("Unrecognized stem override indicator '%s', should be %s"):format( part, generate_list_of_possibilities_for_err( isadj and m_is_adjective.overridable_stems or overridable_stems))) end if base[spec] then if spec == "stem" then parse_err("Can't specify spec for 'stem:' twice (including using 'stem:' along with # or ##)") else parse_err(("Can't specify '%s:' twice"):format(spec)) end end base[spec] = value break elseif part == "#" or part == "##" then if base.stem then parse_err("Can't specify a stem spec ('stem:', # or ##) twice") end base.stem = part break elseif part == "sg" or part == "pl" or part == "both" then if base.number then if base.number ~= part then parse_err("Can't specify '" .. part .. "' along with '" .. base.number .. "'") else parse_err("Can't specify '" .. part .. "' twice") end end base.number = part break elseif part == "indef" or part == "def" or part == "bothdef" then if base.definiteness then if base.definiteness ~= part then parse_err(("Can't specify two conflicting definiteness values; saw '%s' (%s) when existing " .. "definiteness is %s"):format(part, definiteness_code_to_desc[part], definiteness_code_to_desc[base.definiteness])) else parse_err("Can't specify '" .. part .. "' twice") end end base.definiteness = part break elseif not isadj and (part == "weak" or part == "iending" or part == "rstem" or part == "já" or part == "linkasis") or isadj and (m_is_adjective.boolean_property_set[part] or part == "iscomp") or part == "proper" or part == "common" or part == "dem" or part == "builtin" or part == "indecl" or part == "decl?" then if base.props[part] then parse_err("Can't specify '" .. part .. "' twice") end base.props[part] = true break elseif part == "~" then if base.link_lowercase then parse_err("Can't specify '~' twice") end base.link_lowercase = true break elseif isadj and m_table.contains(com.unumut_types, part) then if base.adj_unumut then parse_err("Can't specify two values for reverse u-mutation spec with adjectives") end base.adj_unumut = part break end parse_err("Unrecognized indicator '" .. part .. "'") end end return base end -- Set some defaults (e.g. number and definiteness) now, because they (esp. the number) may be needed -- below when determining how to merge scraped and user-specified properies. local function set_early_base_defaults(base) if is_regular_noun(base) then local function check_err(msg) error(("Lemma '%s': %s"):format(base.lemma, msg)) end if not base.gender then check_err("Internal error: For nouns, gender must be specified") end base.number = base.number or is_proper_noun(base, base.lemma) and "sg" or base.gender == "m" and (base.lemma:find("skapur$") or base.lemma:find("naður$")) and not base.stem and "sg" or "both" base.definiteness = base.definiteness or is_proper_noun(base, base.lemma) and "indef" or "bothdef" process_declnumber(base) base.actual_gender = base.gender if base.declgender then if not base.declgender:find("^[mfn]$") then check_err(("Unrecognized gender '%s' for 'declgender:', should be 'm', 'f' or 'n'"):format( base.declgender)) end base.gender = base.declgender end end end local function parse_inside_and_merge(inside, lemma, scrape_chain) local function parse_err(msg) error(msg .. ": <" .. inside .. ">") end if #scrape_chain >= 10 then local linked_scrape_chain = {} for _, element in ipairs(scrape_chain) do table.insert(linked_scrape_chain, "[[" .. element .. "]]") end parse_err(("Probable infinite loop in scraping; scrape chain is [[%s]] -> %s"):format(lemma, table.concat(linked_scrape_chain, " -> "))) end local base = create_base() base.lemma = lemma base.scrape_chain = scrape_chain parse_inside(base, inside, #scrape_chain > 0) if not base.scrape_spec then -- If we're not scraping the declension from another noun, just return the parsed `base`. -- But don't set early defaults if we're being scraped because it interferes with overriding the number -- and/or definiteness by the noun that is scraping us. if #scrape_chain == 0 then set_early_base_defaults(base) end return base else local retval = com.find_inflection_given_scrape_spec { lemma = lemma, scrape_spec = base.scrape_spec, scrape_is_suffix = base.scrape_is_suffix, scrape_is_uppercase = base.scrape_is_uppercase, infltemp = "is-ndecl", allow_empty_infl = false, inflid = base.scrape_id, parse_off_ending = com.parse_off_final_nom_ending, } local prefix, base_noun, declspec, errmsg = retval.prefix, retval.base_lemma, retval.infl, retval.errmsg if errmsg then base.prefix = prefix base.base_noun = base_noun base.scrape_error = errmsg return base end -- Parse the inside spec from the scraped noun (merging any sub-scraping specs), and copy over the -- user-specified properties on top of it. table.insert(scrape_chain, base_noun) local inner_base = parse_inside_and_merge(declspec.infl, base_noun, scrape_chain) inner_base.lemma = lemma inner_base.prefix = prefix inner_base.base_noun = base_noun -- Add `prefix` to a full variant of the base noun (e.g. a stem spec or full override). We may need -- to adjust the variant to take into account the base noun being a suffix and/or uppercase (e.g. when -- we use [[-dómur]] to generate the inflection of [[vísdómur]] or [[Björn]] to generate the inflection -- of [[Ásbjörn]]). local function add_prefix(form) if base.scrape_is_suffix then form = form:gsub("^%-", "") end if base.scrape_is_uppercase then local first, rest = rmatch(form, "^(.)(.*)$") if first then form = ulower(first) .. rest end end return prefix .. form end -- If there's a prefix, add it now to all the full overrides in the scraped noun, as well as 'decllemma' -- and all stem overrides. if prefix ~= "" then map_all_overrides(inner_base, function(formobj) -- Not if the override contains # or ##, which expand to the full lemma (possibly minus -r -- or -ur). if formobj.form:find("^!") and not formobj.form:find("#") then formobj.form = "!" .. add_prefix(usub(formobj.form, 2)) end end) if inner_base.decllemma then inner_base.decllemma = add_prefix(inner_base.decllemma) end for _, stem in ipairs(overridable_stems) do -- Only actual stems, not (un)imutval; and not if the stem contains # or ##, which -- expand to the full lemma (possibly minus -r or -ur). if inner_base[stem] and stem:find("stem$") and not inner_base[stem]:find("#") then inner_base[stem] = add_prefix(inner_base[stem]) end end end local function copy_properties(plist) -- Copy various properties. for _, prop in ipairs(plist) do if base[prop] ~= nil then inner_base[prop] = base[prop] end end end copy_properties(control_specs) copy_properties(overridable_stems) copy_properties { "gens", "pls", "gender", "number", "definiteness", "decllemma", "declgender", "declnumber", "q", "header", "link_lowercase" } inner_base.footnotes = iut.combine_footnotes(inner_base.footnotes, base.footnotes) -- Copy addnote specs. for _, prop_list in ipairs { "addnote_specs" } do for _, prop in ipairs(base[prop_list]) do m_table.insertIfNot(inner_base[prop_list], prop) end end -- Now copy remaining user-specified specs into the scraped noun `base`. for _, prop_table in ipairs { "overrides", "props" } do for slot, prop in pairs(base[prop_table]) do inner_base[prop_table][slot] = prop end end -- Now determine the defaulted number and definiteness (after copying relevant settings -- but before the check just below that relies on `inner_base.number` being set). set_early_base_defaults(inner_base) -- If user specified 'sg', cancel out any pl overrides, otherwise we'll get an error. if inner_base.number == "sg" then inner_base.pls = nil for slot, _ in pairs(inner_base.overrides) do if slot:find("_p$") then inner_base.overrides[slot] = nil end end end return inner_base end end --[=[ Parse an indicator spec (text consisting of angle brackets and zero or more dot-separated indicators within them). Return value is an object of the form indicated in the comment above create_base(). ]=] local function parse_indicator_spec(angle_bracket_spec, lemma, pagename) if lemma == "" then lemma = pagename end local inside = rmatch(angle_bracket_spec, "^<(.*)>$") assert(inside) local orig_lemma = lemma local orig_lemma_no_links = m_links.remove_links(lemma) lemma = orig_lemma_no_links local base = parse_inside_and_merge(inside, lemma, {}) base.orig_lemma = orig_lemma base.orig_lemma_no_links = orig_lemma_no_links return base end -- Determine if the term has more than one word in it. Normally we just look at the number of words -- at top level. However, it's possible to have a single alternant at top level with multiple words -- in one of the arms, e.g. the equivalent of ((rēspūblica<>,rēs<>pūblica<>)). So if there's only one -- top-level "word" and it's an alternant, check the length of each arm. We also need to check for -- before-text and post-text if there's only one inflected term. local function compute_is_multiword(alternant_multiword_spec) if #alternant_multiword_spec.alternant_or_word_specs > 1 or alternant_multiword_spec.post_text ~= "" then return true end local alternant_or_word_spec = alternant_multiword_spec.alternant_or_word_specs[1] if alternant_or_word_spec.alternants then for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do if #multiword_spec > 1 or multiword_spec.post_text ~= "" or multiword_spec[1] and multiword_spec[1].before_text ~= "" then return true end end end if alternant_or_word_spec.before_text ~= "" then return true end return false end local function set_defaults_and_check_bad_indicators(base) local function check_err(msg) error(("Lemma '%s': %s"):format(base.lemma, msg)) end -- Set default values. local regular_noun = is_regular_noun(base) if not base.adjspec and base.props.builtin then set_builtin_defaults(base) end if not regular_noun and not base.adjspec then for _, control_spec in ipairs(control_specs) do if base[control_spec] then check_err(("'%s' cannot be specified with pronouns"):format(control_spec)) end end end if not regular_noun then if base.declgender then check_err("'declgender' can only be specified with regular nouns") end return end -- Check for bad indicator combinations. if base.imut and base.unimut then check_err("'imut' and 'unimut' specs cannot be specified together") end if base.umut and base.unumut then check_err("'umut' and 'unumut' specs cannot be specified together") end if base.unimut and base.unumut then check_err("'unimut' and 'unumut' specs cannot be specified together") end if base.declnumber == "pl" and (base.gens or base.pls) then check_err("Cannot set genitive or plural specs after the gender in plural-only lemmas") end if base.plvstem and not base.plstem then check_err("When 'plvstem:' given, 'plstem:' must also be given") end -- Compute whether i-mutation stems are needed. -- First check for 'imut' set by user. if not base.need_imut then -- might be set by the detected declension if base.imut then for _, formobj in ipairs(base.imut) do if formobj.form == "imut" then base.need_imut = true break end end end end -- Then check for 'unimut' set by user. if not base.need_imut then if base.unimut then for _, formobj in ipairs(base.unimut) do if formobj.form == "unimut" then base.need_imut = true break end end end end -- Then check all overrides for any beginning with a single ^. if not base.need_imut then map_all_overrides(base, function(formobj) if formobj.form:find("^%^") and not formobj.form:find("^%^%^") then base.need_imut = true return true end end) end if base.imutval and not base.need_imut then check_err("'imutval:...' specified but 'imut' and 'unimut' not specified and no forms need i-mutation") end if base.unimutval and not base.need_imut then check_err("'unimutval:...' specified but 'imut' and 'unimut' not specified and no forms need i-mutation") end end local function set_all_defaults_and_check_bad_indicators(alternant_multiword_spec) -- Used when determining how to link definite-only and plural-only nouns. alternant_multiword_spec.is_multiword = compute_is_multiword(alternant_multiword_spec) iut.map_word_specs(alternant_multiword_spec, function(base) base.is_multiword = alternant_multiword_spec.is_multiword set_defaults_and_check_bad_indicators(base) for _, global_prop in ipairs { "q", "header" } do if base[global_prop] then if alternant_multiword_spec[global_prop] == nil then alternant_multiword_spec[global_prop] = base[global_prop] elseif alternant_multiword_spec[global_prop] ~= base[global_prop] then error(("With multiple words or alternants, set '%s' on only one of them or make them all agree"): format(global_prop)) end end end if base.props.builtin then alternant_multiword_spec.saw_builtin = true else alternant_multiword_spec.saw_non_builtin = true end if base.props.indecl then alternant_multiword_spec.saw_indecl = true else alternant_multiword_spec.saw_non_indecl = true end if base.props["decl?"] then alternant_multiword_spec.saw_unknown_decl = true else alternant_multiword_spec.saw_non_unknown_decl = true end end) end local function expand_property_sets(base) base.prop_sets = { {} } -- Construct the prop sets from all combinations of control specs, in case any given spec has more than one -- possibility. for _, control_spec in ipairs(control_specs) do local specvals = base[control_spec] -- Handle unspecified control specs. if not specvals then specvals = { false } end if #specvals == 1 then for _, prop_set in ipairs(base.prop_sets) do -- Convert 'false' back to nil prop_set[control_spec] = specvals[1] or nil end else local new_prop_sets = {} for _, prop_set in ipairs(base.prop_sets) do for _, specval in ipairs(specvals) do local new_prop_set = m_table.shallowCopy(prop_set) new_prop_set[control_spec] = specval table.insert(new_prop_sets, new_prop_set) end end base.prop_sets = new_prop_sets end end end -- Return the most likely ending to add to a stem form (e.g. from the feminine singular or the plural) to -- to get the lemma form (masculine singular). -- We use the following rules: -- 1. Stems in -nn or -ll take -ur. -- 2. Stems in -Vn or -Vl double the last letter, but not if this will result in contraction (the default -- for nouns in -[aiu]nn and -[aiu]ll, but for adjectives only in -inn), in which case -ur is added. -- 3. Stems in -Cn, -Cl, -r or -s remain unchanged. -- 4. Stems in a vowel add -r. -- 5. Remaining stems add -ur. -- Exceptional lemma forms for adjectives will need to be handled through a slash substitution spec or by -- directly specifying the lemma after a colon. local function stem_to_masc_sg_lemma_ending(stem, isadj) if stem:find("nn$") or stem:find("ll$") then return "ur" elseif not isadj and (stem:find("a[nl]$") or stem:find("[^eE]i[nl]$") or stem:find("[^aA]u[nl]$")) or isadj and stem:find("[^eE]in$") then return "ur" elseif stem:find(com.vowel_c .. "[nl]$") then return stem:sub(-1) elseif stem:find("[nlrs]$") then return "" elseif rfind(stem, com.vowel_c .. "$") then return "r" else return "ur" end end -- For a plural-only lemma, synthesize a likely singular lemma. It doesn't have to be theoretically correct as long as -- it generates all the correct plural forms. local function synthesize_singular_lemma(base) local lemma_determined -- Loop over all property sets in case the user specified multiple ones (e.g. using different control specs). If -- we try to reconstruct different lemmas for different property sets, we'll throw an error below. for _, props in ipairs(base.prop_sets) do local function interr(msg) error(("Internal error: For lemma '%s', %s: %s"):format(base.lemma, msg, dump(props))) end -- `ending` refers to the plural ending but is not currently used much. Instead, in add_decl(), when we process -- pl-only terms, we set the nom_pl to "*" so that the lemma is used directly. local stem, lemma, ending, sg_ending, default_unumut if base.gender == "m" or base.gender == "f" then stem, ending = rmatch(base.lemma, "^(.*)([aiu]r)$") if stem then -- masc: -- -- [[tónleikar]] "concert"; [[feðgar]] "father and son"; [[hafrar]] "oats" (dat pl höfrum); -- [[fjármunir]] "goods, property"; [[Fljótsdælir]] "inhabitants of Fljótsdalur (a valley)" -- (occurs definite, needs 'dem', no unimut); [[Ásmegir]] "sons of the Gods" (occurs definite, dat -- pl Ásmögum, gen pl Ásmaga, i.e. needs 'def' and 'unimut'); similarly [[ljóðmegir]]; -- [[buskuleggir]] "?" (has 'j' in dat pl [[buskuleggjum]], gen pl [[buskuleggja]]; [[Bekkir]] -- (place name; has 'j' in dat pl [[Bekkjum]], gen pl [[Bekkja]]) -- -- fem: -- [[frönskur]] "French fries" (with unumut); [[hjólbörur]] "wheelbarrow" (with unumut); [[buxur]] -- "trousers, pants", [[hættur]] "bedtime; quitting time" (with unimut); [[herðar]] "shoulders"; -- [[limar]] "branches"; [[öfgar]] "exaggeration, extreme" (no unumut); [[drefjar]] -- "stains, traces"; [[viðjar]] "chains, fetters"; many others in -jar, but the -j- is throughout -- the plural; [[svalir]] "balcony; porch"; [[dyr]] "doorway" (uses decllemma:dyrir) if ending == "ur" then -- FIXME: Does -ur as masculine plural ending occur? What should the singular be? sg_ending = base.gender == "f" and "a" or nil default_unumut = "unumut" else sg_ending = base.gender == "f" and "" or nil end if not sg_ending then sg_ending = stem_to_masc_sg_lemma_ending(stem) end elseif base.lemma:find("ær$") then -- [[barnatær]], [[fultær]], proper name [[Tær]] stem = base.lemma sg_ending = "" else error(("Masculine or feminine plural-only lemma '%s' should end in -ar, -ir or -ur"):format(base.lemma)) end elseif base.gender == "n" then -- Neuters in -i. Examples: [[fræði]] "branch of knowledge", [[jafndægri]] "equinox", [[meðmæli]] -- "recommendation", [[sannindi]] "truth", [[skæri]] "pair of scissors", [[vísindi]] "knowedge, learning". -- unimut is possible and occurs in [[læti]] "behavior, demeanor", [[ólæti]] "noise, racket". stem, ending = rmatch(base.lemma, "^(.*[^eE])(i)$") if stem then sg_ending = "i" end if not stem then -- Weak neuters in -u like [[gleraugu]] "glasses/spectacles". stem, ending = rmatch(base.lemma, "^(.*[^aA])(u)$") if stem then sg_ending = "a" default_unumut = "unumut" end end if not stem then -- Generally, plural will look like singular, with no ending in the plural (but there will be umut -- if possible). Examples: [[feðgin]] "father and daughter", [[hjón]] "married couple", [[jól]] -- "Christmas", [[lok]] "end"; [[jarðgöng]] "tunnel" needing 'unumut'. stem = base.lemma sg_ending = "" default_unumut = "unumut" end else interr(("unrecognized gender '%s'"):format(base.gender)) end if default_unumut and not props.unumut and not props.umut and not props.unimut then props.unumut = { form = default_unumut, defaulted = true } end if props.unumut and props.unimut then interr("shouldn't see both 'unumut' and 'unimut' set in plural-only lemma") end if props.unumut and props.unumut.form:find("^un") then stem = apply_reverse_u_mutation(stem, props.unumut.form, not props.unumut.defaulted) if props.umut then interr("shouldn't see both 'unumut' and 'umut' set in plural-only lemma") end props.umut = { form = rsub(props.unumut.form, "^un", ""), footnotes = props.unumut.footnotes, defaulted = props.unumut.defaulted } props.unumut = nil end if props.unimut and props.unimut.form:find("^un") then stem = apply_reverse_i_mutation(stem, base.unimutval, "error if unmatchable") if props.imut then interr("shouldn't see both 'unimut' and 'imut' set in plural-only lemma") end props.imut = { form = rsub(props.unimut.form, "^un", ""), footnotes = props.unimut.footnotes } props.unimut = nil end lemma = stem .. sg_ending if lemma_determined and lemma_determined ~= lemma then error(("Attempt to set two different singular lemmas '%s' and '%s'"):format(lemma_determined, lemma)) end lemma_determined = lemma end base.lemma = lemma_determined base.lemma_ending = ending or "" end -- For a nominative definite lemma, synthesize the corresponding indefinite lemma. Note that a plural definite lemma may -- need to be processed twice, first to convert to plural indefinite and then to convert to singular indefinite using -- synthesize_singular_lemma(). local function synthesize_indefinite_lemma(base) local lemma_determined -- Loop over all property sets in case the user specified multiple ones (e.g. using different control specs). If -- we try to reconstruct different lemmas for different property sets, we'll throw an error below. for _, props in ipairs(base.prop_sets) do local function interr(msg) error(("Internal error: For lemma '%s', %s: %s"):format(base.lemma, msg, dump(props))) end -- There are only 6 clitic articles, depending on the combination of gender and number: -- singular: m = -inn, f = -in, n = -ið; plural: m = -nir, f = -nar, n = -in. The two beginning in n- aren't -- problematic because in all cases they simply append to the actual form. The remaining four, however, drop -- the i- before an ending [aiu]. This means we can uniquely reconstruct the dropped vowel if we see e.g. -- -að or -uð in place of -ið. But if we see -ið, we don't know whether the lemma ended in -i or a consonant. -- And in general it's important to know because it affects some forms; e.g. compare definite neuter 'knippið' -- from [[knippi]] "bundle, bunch" with 'klappið' from [[klapp]] "applause; pat, stroke". The former has -- definite genitive 'knippisins' and the latter 'klappsins'. And in fact, all three genders commonly have -- both consonant-ending and i-ending nouns in the singular. This means we need an indicator to distinguish -- them. Probably easiest is 'iending'; reusing 'weak' won't work so well because neuters in -i, and sometimes -- feminines in -i, are considered strong. local function process_n_clitic(clitic) local lemma = base.lemma:match("^(.*)" .. clitic .. "$") if not lemma then error(("Lemma '%s' declared as %s %s should end in clitic '-%s'"):format(base.lemma, gender_code_to_desc[base.gender] or "NONE", number_code_to_desc[base.number] or "NONE", clitic)) end return lemma end local function process_i_clitic(clitic) local clitic_cons_end = clitic:match("^i(.*)$") if not clitic_cons_end then interr(("clitic '%s' should begin with 'i'"):format(clitic)) end local lemma_begin, ending = base.lemma:match("^(.*)([aiu])" .. clitic_cons_end .. "$") if not lemma_begin then error(("Lemma '%s' declared as %s %s should end in clitic '-%s' or in '-a%s' or '-u%s'"):format( base.lemma, gender_code_to_desc[base.gender] or "NONE", number_code_to_desc[base.number] or "NONE", clitic, clitic_cons_end, clitic_cons_end)) end if ending == "a" or ending == "u" then if base.props.iending then error(("Property 'iending' cannot be specified because definite lemma '%s' does not end in '-%s'"): format(base.lemma, clitic)) end return lemma_begin .. ending end if base.props.iending then return lemma_begin .. "i" else return lemma_begin end end local clitic = clitic_articles[base.gender]["nom_" .. (base.number == "pl" and "p" or "s")] local lemma if clitic:find("^n") then lemma = process_n_clitic(clitic) else lemma = process_i_clitic(clitic) end if lemma_determined and lemma_determined ~= lemma then error(("Attempt to set two different indefinite lemmas '%s' and '%s'"):format(lemma_determined, lemma)) end lemma_determined = lemma end base.lemma = lemma_determined base.lemma_ending = "" end -- For an adjectival lemma, synthesize the masc singular form. local function synthesize_adj_lemma(base) if base.props.indecl then base.decl = "indecl" return elseif base.props["decl?"] then base.decl = "decl?" return else base.decl = "adj" local adjspec = base.adjspec if not adjspec then error("Internal error: synthesize_adj_lemma() called without a parsed adjective spec in `base.adjspec`") end if adjspec.lemma then base.lemma = adjspec.lemma elseif adjspec.subspec then local from, to = adjspec.subspec.from, adjspec.subspec.to if from then local beginpart = base.lemma:match("^(.*)" .. m_string_utilities.pattern_escape(from) .. "$") if not beginpart then error(("Adjective slash substitution spec '/%s/%s' didn't match form '%s'"):format( from, to, base.lemma)) end base.lemma = beginpart .. to else local num_to_remove if base.number == "pl" then num_to_remove = (base.gender == "m" or base.gender == "f") and 2 or 0 elseif base.gender == "m" then error(("Single-part adjective slash substitution spec '/%s' not allowed with masculine-singular " .. "adjective form '%s'; if necessary, use a two-part spec or specify the lemma directly after " .. " a colon"):format(from, base.lemma)) else num_to_remove = base.gender == "f" and 0 or 1 end base.lemma = usub(base.lemma, 1, -num_to_remove - 1) .. to end else local stem, stem_is_lemma if base.props.iscomp then base.adj_is_weak = true if base.number ~= "pl" and base.gender == "n" then stem = base.lemma:match("^(.*)a$") if not stem then error(("Neuter singular weak comparative adjective form should end in -a: %s" ):format(base.lemma)) end stem = stem .. "i" else if not base.lemma:find("i$") then error(("Plural or masculine/feminine singular weak comparative adjective form should " .. "end in -i: %s"):format(base.lemma)) end stem = base.lemma end stem_is_lemma = true elseif base.number == "pl" then stem = base.lemma:match("^(.*[^Aa])u$") if stem then base.adj_is_weak = true end if not stem then if base.gender == "m" then stem = base.lemma:match("^(.*)ir$") if not stem then error(("Masculine plural strong adjective form should end in -ir: %s"):format(base.lemma)) end elseif base.gender == "f" then stem = base.lemma:match("^(.*)ar$") if not stem then error(("Feminine plural strong adjective form should end in -ar: %s"):format(base.lemma)) end else stem = base.lemma end end else if base.gender == "m" then stem = base.lemma:match("^(.*[^Ee])i$") if stem then base.adj_is_weak = true else -- Otherwise the form is strong and should remain as is. stem = base.lemma stem_is_lemma = true end elseif base.gender == "f" or base.gender == "n" then stem = base.lemma:match("^(.*)a$") if stem then base.adj_is_weak = true end if not stem then if base.gender == "n" then error(("No automatic rules for inferring the adjective lemma from strong neuter form " .. "'%s'; you must use a slash substitution spec such as 'tvöfalt<adj/dur>' (which " .. "chops off the last letter and replaces it with 'dur') or 'ryðfrítt<adj/tt/r>' " .. "(which chops off 'tt' and replaces it with 'r'), or directly specify the lemma " .. "using e.g. 'tvö<adj:tveir>'" ):format(base.lemma)) else stem = base.lemma end end end end if not stem_is_lemma then if base.adj_unumut then stem = apply_reverse_u_mutation(stem, base.adj_unumut, "error if unmatchable") end base.lemma = stem .. stem_to_masc_sg_lemma_ending(stem) else base.lemma = stem end end end end -- Determine the declension and stem based on the lemma and gender. The declension is set in base.decl and the stem in -- base.stem if not already set by the user. local function determine_declension(base) local stem = nil local ending local default_props = {} -- Determine declension if base.props.indecl then base.decl = "indecl" stem = base.lemma elseif base.props["decl?"] then base.decl = "decl?" stem = base.lemma elseif base.gender == "m" then if not stem then stem, ending = rmatch(base.lemma, "^(.*[ÁáÆæ])(r)$") if stem then -- in -ár: -- [[nár]] "corpse", [[sár]] "tub (archaic)", [[hár]] "thole, oarlock (archaic)", [[hár]] -- "spiny dogfish (archaic)" (with v-infix), [[kljár]] "weaving stone (archaic)", [[ljár]] "scythe", -- [[skjár]] "video screen, display", [[már]] "seagull", [[sjár]] "sea", [[snjár]] "snow" (with -- v-infix); vs. stems ending in -r: [[ár]] "? (archaic)", [[lár]] "wooden box for wool", [[klár]] -- "inferior horse, nag", [[pílár]] "slat, fence post; spoke (dated)", [[kentár]] "centaur" -- -- in -ær: -- [[glær]] "sea", [[skær]] "? (obsolete)", [[blær]] "gentle breeze", [[bær]] "farm; town", [[óbær]] -- "?", [[sær]] "sea", [[snær]] "snow" -- -- We used to also drop -r by default from the stem in -ýr words, but this doesn't make sense for -- adjectives and only half makes sense for nouns, so we don't do it any more. We have stems without -r: -- [[ýr]] "yew", [[býr]] "town, farm", [[gnýr]] "clash, rumble; blue wildebeest", [[týr]] "hero; god", -- also many proper names; vs. stems ending in -r: [[fýr]] "dude, guy", [[lýr]] "pollock", [[sýr]] -- "? (poetic)", [[ýr]] "? (obsolete)", [[hlýr]] "? (obsolete)", [[glýr]] "? (obsolete)" base.decl = "m" end end if not stem then stem = rmatch(base.lemma, "^(.*[Aa]ur)$") if stem then -- [[maur]] "ant", [[aur]] "loam, mud", [[gaur]] "ruffian", [[paur]] "devil; enmity", -- [[saur]] "dirt; excrement", [[staur]] "post", [[ljósastaur]] "lamp post" base.decl = "m" end end if not stem then -- There must be at least one vowel; lemmas like [[bur]] don't count. stem, ending = rmatch(base.lemma, "^(.*" .. com.vowel_or_hyphen_c .. ".*)(ur)$") if stem then if stem:find("skap$") and not base.stem then -- tons of words in -skapur base.decl = "m-skapur" elseif stem:find("nað$") and not base.stem then -- lots of words in -naður base.decl = "m-naður" default_props.umut = "uUmut" else if base.stem == base.lemma then -- [[akur]] "field" etc. where the stem includes the final -r stem = base.stem ending = "" -- not actually used default_props.con = "con" end -- [[hestur]] "horse" and lots of others base.decl = "m" end end end if not stem then stem = rmatch(base.lemma, "^(.*[Ee][iy]r)$") if stem then -- in -eir (all include -r in the stem): -- [[geir]] "?", [[eir]] "copper", [[leir]] "clay", [[Geir]] (male given name) -- -- in -eyr, including -r in the stem: -- [[reyr]] "reed", [[Reyr]] (male given name) -- in -eyr, not including -r in the stem: -- [[þeyr]] "thaw, thawing wind", [[Þeyr]] (male given name), [[Freyr]] (male given name) base.decl = "m" end end if not stem then -- There must be at least one vowel (although there don't appear to be any single-syllable -- lemmas ending in -ir other than in -eir). stem, ending = rmatch(base.lemma, "^(.*" .. com.vowel_or_hyphen_c .. ".*)(ir)$") if stem then -- [[læknir]] "physician" and many others -- [[bróðir]], [[faðir]] are r-stems if base.props.rstem then base.decl = "m-rstem" base.need_imut = true else base.decl = "m-ir" end end end if not stem then stem, ending = rmatch(base.lemma, "^(.*l)(l)$") if stem then if is_proper_noun(base, stem) and stem:find("kel$") then base.decl = "m-kell" else if not base.stem and (rfind(stem, com.cons_c .. "[aiu]l$") or stem:find("^[aiuAIU]l$")) then -- [[gaffall]] "fork" (dat pl [[göfflum]]), [[þumall]] "thumb"; [[ekkill]] "widower"; -- [[spegill]] "mirror"; [[segull]] "magnet"; [[öxull]] "axis; axle"; etc. Note that the check -- for a consonant preceding the a/i/u is important as there are words like [[manúall]] -- "manual", [[ritúall]] "ritual", [[kokteill]] "cocktail", [[feill]] "flaw, error", [[deill]] -- "dispute???" (rare, regional), [[haull]] "hernia", [[straull]] "? (rare, regional)" that -- don't have contraction. Beware of the rare word [[síill]] "sieve? strainer?" that per BÍN -- does contract to síl- before vowels. Currently the code to handle contraction will throw an -- error if you attempt to contract that word, but you can use 'vstem:...'. -- -- There are also lots of words in a vowel other than a/i/u followed by -ll, such as [[bíll]] -- "car", [[áll]] "eel", [[konsúll]] "consul", [[þræll]] "slave", [[hvoll]] "hill", [[stóll]] -- "chair", etc. In these, the final -l is the nominative singular ending, as above. -- -- Note that if the user overrode the stem (e.g. using '#' as with [[Ármann]]), we don't -- default to contraction as it may cause an error to be thrown. default_props.con = "con" end base.decl = "m" end end end if not stem then stem, ending = rmatch(base.lemma, "^(.*n)(n)$") if stem then if not base.stem and (rfind(stem, com.cons_c .. "[aiu]n$") or stem:find("^[aiuAIU]n$")) then -- As with -all/-ill/-ull although there are fewer such words in -nn. Examples: [[aftann]] "evening" -- (dat pl öftnum), [[arinn]] "hearth, fireplace" (dat pl örnum), [[drottinn]] "lord", [[himinn]] -- "sky, heaven", [[morgunn]] "morning", [[jötunn]] "giant", etc. -- -- There are also lots of words in a vowel other than a/i/u followed by -nn, such as [[fleinn]] -- "spear", [[steinn]] "rock", [[prjónn]] "knitting needle", [[daunn]] "stink", [[húnn]] "knob". -- In these, the final -n is the nominative singular ending, as above. -- -- Note that if the user overrode the stem (e.g. using '#' as with [[Ármann]]), we don't default -- to contraction as it may cause an error to be thrown. default_props.con = "con" end base.decl = "m" end end if not stem and not base.props.weak then stem, ending = rmatch(base.lemma, "^(.*[aóæ]nd)(i)$") if stem then -- [[nemandi]] "student" and many others; terms in -jandi like [[byrjandi]] -- "beginner", [[seljandi]] "seller" umlaut to -jend- in the plural instead of -ind- -- also terms in -óndi (probably all compounds of [[bóndi]] "farmer") and in -ændi -- (probably all compounds of [[frændi]]). Terms like [[andi]] "breath, spirit" and -- [[heiðasandi]] "heath sand?" need '.weak' to disable this, as does [[fjandi]] in -- the meaning "devil, demon" (vs. "enemy", which has plural [[fjendur]]). Terms like -- [[vandi]] "trouble; responsibility; custom, habit" and compounds are singular-only. base.decl = "m-ndi" if not stem:find("ænd$") then base.need_imut = true end if stem:find("jand$") then default_props.imutval = "je" end end end if not stem then stem, ending = rmatch(base.lemma, "^(.*)([ia])$") if stem then -- [[tími]] "time, hour" and many others; [[herra]] "gentleman" ([[sendiherra]] "ambassador"), -- [[séra]]/[[síra]] "reverend" base.decl = "m-weak" -- Recognize -ingi and make automatically j-infixing, but only when a vowel precedes -- (not [[ingi]], [[Ingi]], [[stingi]], [[þvingi]]). Use `-j` to turn this off. if ending == "i" and rfind(stem, com.vowel_or_hyphen_c .. ".*ing$") then default_props.j = "j" elseif ending == "i" and rfind(stem, com.vowel_or_hyphen_c .. ".*ar$") then default_props.umut = "uUmut" end end end if not stem then stem = rmatch(base.lemma, "^(.*ó)$") if stem then -- [[kanó]] "canoe", [[pesó]] "peso", [[Plútó]] "Pluto", [[Markó]] (male given name), etc. base.decl = "m-ó" end end if not stem then -- Miscellaneous masculine terms without ending stem = base.lemma base.decl = "m" end elseif base.gender == "f" then if not stem then stem, ending = rmatch(base.lemma, "^(.*)(a)$") if stem then base.decl = "f-weak" end end if not stem then stem, ending = rmatch(base.lemma, "^(.*[^eE])(i)$") if stem then base.decl = "f-i" end end if not stem and not base.stem then -- Don't match when base.stem is set, e.g. [[gimbur]] "female lamb", where the -ur is part of the stem stem, ending = rmatch(base.lemma, "^(.*[^aA])(ur)$") if stem and rfind(stem, com.vowel_or_hyphen_c) then if is_proper_noun(base, stem) then -- [[Auður]], [[Heiður]], [[Ingveldur]], [[Móeiður]], [[Þórelfur]], [[-frídur]] ([[Gunnfríður]], -- [[Hólmfríður]], [[Málfríður]], [[Sigfríður]]), [[Gerður]] ([[Hallgerður]], [[Ingigerður]], -- [[Þorgerður]]), [[Gunnur]] ([[Arngunnur]], [[Hildigunnur]]), [[Heiður]] ([[Aðalheiður]], -- [[Arnheiður]], [[Brynheiður]], [[Ragnheiður]]), [[Hildur]] ([[Ásthildur]], [[Berghildur]], -- [[Brynhildur]], [[Geirhildur]], [[Gunnhildur]], [[Ragnhildur]], [[Þórhildur]]), [[Ástríður]] -- (related names [[Guðríður]], [[Sigríður]], [[Þuríður]]), [[Þrúður]] [also a man's name] -- ([[Jarþrúður]], [[Jarðþrúður]], [[Sigþrúður]]) -- -- also with company/organization names like [[Berghildur]], [[Gunnhildur]]; likewise place names -- like [[Þuríður]] base.decl = "f-acc-dat-i" else base.decl = "f-ur" end end end if not stem and base.props.rstem then stem, ending = rmatch(base.lemma, "^(.*[^eE])(ir)$") if stem and base.props.rstem then -- [[dóttir]], [[móðir]], [[systir]] base.decl = "f-rstem" if not stem:find("syst$") then base.need_imut = true end end end if not stem then stem = rmatch(base.lemma, "^(.*ung)$") if stem then base.decl = "f-ung" end end if not stem then stem = rmatch(base.lemma, "^(.*ing)$") if stem then base.decl = "f-ing" end end if not stem then stem = rmatch(base.lemma, "^(.*[áóúÁÓÚ])$") if stem then base.decl = "f-long-vowel" if rfind(stem, "[óÓ]$") then base.need_imut = true end end end if not stem and not base.stem then -- Not when base.stem is set, which includes [[Ýr]], following the regular endingless f declension where -- -r is part of the stem. stem, ending = rmatch(base.lemma, "^(.*[ýÝæÆ])(r)$") if stem then -- [[kýr]] "cow", [[sýr]] "sow (archaic)", [[ær]] "ewe" and compounds base.decl = "f-long-umlaut-vowel-r" default_props.unimut = "unimut" end end if not stem then stem = rmatch(base.lemma, "^(.*[^aA]un)$") if stem and rfind(stem, com.vowel_or_hyphen_c) then -- [[pöntun]] "order (in commerce)"; [[verslun]] "trade, business; store, shop"; [[efun]] "doubt"; -- [[bötun]] "improvement"; [[örvun]] "encouragement; stimulation" (pl. örvanir); etc. -- Exclude words in -aun like [[baun]] "bean", [[laun]] "secret", [[raun]] "experience". -- Some words need a different indicator, e.g. [[örvun]] "encouragement; stimulation" (pl. örvanir), -- [[fjölgun]] "increase, proliferation" (pl. fjölganir), which need "unUmut". base.decl = "f" default_props.unumut = "unuUmut" end end if not stem then -- Miscellaneous feminine terms without ending stem = base.lemma base.decl = "f" -- A function here means we resolve it to its actual value later. We don't want to trigger -- unumut if the user specified v-infix or any type of u-mutation (e.g. 'uUmut' in [[ætlan]]), -- or if the last vowel of the term is 'a' ([[dragt]], [[aukavakt]]). default_props.unumut = function(base, props) if base.vstem or props.v and props.v.form == "v" or props.umut or rfind(stem, "[Aa]" .. com.cons_c .. "*$") then return nil else return { form = "unumut", defaulted = true } end end end elseif base.gender == "n" then if not stem then stem, ending = rmatch(base.lemma, "^(.*)(a)$") if stem then base.decl = "n-weak" end end if not stem then -- stem actually includes -é but due to the change to já we include it in the ending stem, ending = rmatch(base.lemma, "^(.*)(é)$") if stem then if base.props["já"] then -- Indicator 'já' for [[tré]], [[hné]]/[[kné]], etc. base.decl = "n-já" else -- [[té]] (letter T), etc. stem = stem .. "é" base.decl = "n" end end end if not stem then stem, ending = rmatch(base.lemma, "^(.*[kKgG])(i)$") if stem then base.decl = "n-i" default_props.j = "j" end end if not stem then stem, ending = rmatch(base.lemma, "^(.*[^eE])(i)$") if stem then base.decl = "n-i" end end if not stem then -- -ur preceded by a consonant and at least one vowel. stem = rmatch(base.lemma, "^(.*" .. com.vowel_or_hyphen_c .. ".*" .. com.cons_c .. "ur)$") if stem then base.decl = "n" default_props.con = "con" default_props.defcon = "defcon" end end if not stem then -- Miscellaneous neuter terms without ending stem = base.lemma base.decl = "n" end else error(("Internal error: `base.gender` is '%s' but should be 'm', 'f' or 'n'"):dump(base.gender)) end if base.stem then -- This isn't necessarily accurate but doesn't really matter. We only record the lemma ending to help with -- contraction of definite clitics in the nominative singular, and in the cases where the user gives an explicit -- stem, it's usually with # (meaning the ending is null) or ## (meaning the ending ends in -r), and in both -- cases there's no contraction of initial vowels in definite clitics in any case. base.lemma_ending = "" else base.stem = stem base.lemma_ending = ending or "" end for k, v in pairs(default_props) do if not base[k] then if control_spec_set[k] then for _, props in ipairs(base.prop_sets) do if type(v) == "function" then props[k] = v(base, props) else props[k] = { form = v, defaulted = true } end end else base[k] = v end end end track("decl/" .. base.decl) end local function determine_default_masc_dat_sg(base, props) -- We only need to compute the default dative singular for regular masculines (not masculines in -ir, -ó, -i, etc.). -- These other types have specific defaults for the entire type. if base.decl ~= "m" or base.number == "pl" then return end local default_dat_sg if base.overrides.dat_s then -- Track explicit override. track("masc-dat-sg-override") end local stem = base.stem if props.j and props.j.form == "j" then -- Stems with j-infix normally have null dative even if they end in two consonants, e.g. -- [[belgur]] "bellows; skin", [[fengur]] "profit", [[flekkur]] "spot, fleck", [[serkur]] -- "shirt", [[stingur]] "sting" default_dat_sg = "" elseif stem:find(com.vowel_or_hyphen_c .. ".*[iu]ng$") then -- Stems in suffix -ing or -ung normally have indef dat -i, def dat null default_dat_sg = { indef = "i", def = "" } elseif stem:find("x$") or (rfind(stem, com.cons_c .. com.cons_c .. "$") and not stem:find("kk$") and not stem:find("pp$")) then -- Other stems in two consonants normally have dat -i, but those in -kk or -pp normally -- don't, so exclude them and require explicit specification default_dat_sg = "i" elseif not rfind(stem, com.vowel_c .. "$") and is_proper_noun(base, stem) then -- proper noun whose stem does not end in a vowel default_dat_sg = "i" elseif props.con and props.con.form == "con" then default_dat_sg = "i" elseif rfind(stem, com.vowel_c .. "r?$") then default_dat_sg = "" elseif base.lemma:find("ll$") then -- nouns in -ll without contraction, which generally includes those not in -all/-ill/-ull plus a few in these -- endings such as [[panill]] "paneling" (rare variant of [[panell]]), [[kórall]] "coral", [[kristall]] -- "crystal", [[kanill]] "cinnamon" (also [[kanell]]); only a few exceptions, such as [[rafall]] "generator", -- which optionally contracts and has with dati/-:i without contraction; [[hvoll]] "hill", [[kokkáll]] -- "cuckold", [[páll]] "spade, pointed shovel", [[þræll]] "slave" with dat-:i/-; [[hóll]] "hill", [[hæll]] -- "heel", [[stóll]] "chair", which are dat-:i[footnote]/- with a footnote variously indicating that the -- dative in -i occurs only in fixed expressions, compounds, place names, etc. default_dat_sg = "" elseif base.lemma:find("nn$") then -- nouns in -nn without contraction, which generally includes those not in -ann/-inn/-unn; there are fewer of -- these than the corresponding nouns in -ll and they have default dative i/i; only exceptions I can find are -- [[húnn]] "knob", [[tónn]] "tone (music)", [[dúnn]] "down (feathers)"", which have dati:-/i. default_dat_sg = "i" elseif base.overrides.def_dat_s and base.definiteness == "def" then -- OK; user supplied def_dat_s override for a definite-only lemma elseif base.overrides.dat_s and base.overrides.dat_s.indef and base.definiteness == "indef" then -- OK; user supplied dat_s override with indefinite setting, for an indefinite-only lemma elseif base.overrides.dat_s and base.overrides.dat_s.indef and base.overrides.def_dat_s then -- OK; user supplied dat_s override with indefinite setting and def_dat_s override, which -- together provide both indefinite and definite values elseif base.overrides.dat_s and not base.overrides.dat_s.def then error(("Saw masculine stem '%s' and dative singular override of just the indefinite ending, but " .. "requires both the indefinite and definite endings of the dative singular in the form 'datINDEF/DEF'"): format(stem)) elseif not base.overrides.dat_s then local exceptions = "exceptions are nouns in -ir, -ó or -i; proper nouns; plural-only nouns; nouns with " .. "stem contraction or j-infix; nouns whose stem ends in two or more consonants, except for -kk and " .. "-pp; and nouns whose stem ends in a vowel or vowel + r" if base.definiteness == "indef" then error(("Saw masculine stem '%s' and no dative override: Most indefinite-only masculine nouns must " .. "explicitly specify the indefinite ending of the dative singular using an override of the form " .. "'datINDEF'; %s"):format(stem, exceptions)) else error(("Saw masculine stem '%s' and no dative override: Most masculine nouns must explicitly specify " .. "the indefinite and definite endings of the dative singular using an override of the form " .. "'datINDEF/DEF'; %s"):format(stem, exceptions)) end end props.default_dat_sg = default_dat_sg end -- Determine the stems and other properties to use for each property set. The list of such properties is given in the -- comment above create_base(), along with the explanation of what a property set is and why we have multiple such -- property sets (generally, one per combination of control specs such as 'con,-con' and 'umut,uUmut'). There are -- currently 9 singular stems and a corresponding 9 plural stems. local function determine_props(base) -- Now determine all the props for each prop set. for _, props in ipairs(base.prop_sets) do -- Determine the default dative singular for masculine nouns using declension "m". determine_default_masc_dat_sg(base, props) -- Almost all nouns have dative plural -um, which triggers u-mutation, so we need to compute the u-mutation -- stem using "umut" if not specifically given. Set `defaulted` so an error isn't triggered if there's no -- special u-mutated form. local props_umut = props.umut if not props_umut and (not props.unumut or props.unumut.form:find("^%-")) then props_umut = { form = "umut", defaulted = true } end -- First do all the stems, handling overall and plural-specific stems separately. for _, prefix in ipairs { "", "pl_" } do local base_stem, base_vstem if prefix == "" then base_stem = base.stem base_vstem = base.vstem else base_stem = base.plstem base_vstem = base.plvstem end -- The plstem is almost never set, so don't do a lot of unnecessary computation. if prefix == "pl_" and not base_stem then break end local stem, nonvstem, umut_nonvstem, imut_nonvstem, vstem, umut_vstem, imut_vstem, null_defvstem, umut_null_defvstem if props.unumut and not props.unumut.form:find("^%-") then umut_nonvstem = base_stem nonvstem = apply_reverse_u_mutation(umut_nonvstem, props.unumut.form, not props.unumut.defaulted) stem = nonvstem if base.need_imut then imut_nonvstem = apply_i_mutation(nonvstem, base.imutval) end if base_vstem then error(("Don't currently know how to combine '%svstem:' with 'unumut' specs"):format( prefix == "pl_" and "pl" or "")) end if props.con and props.con.form == "con" then umut_vstem = com.apply_contraction(base_stem) else umut_vstem = base_stem end vstem = apply_reverse_u_mutation(umut_vstem, props.unumut.form, not props.unumut.defaulted) if base.need_imut then imut_vstem = apply_i_mutation(vstem, base.imutval) end local props_unumut_form = props.unumut.form if props.defcon and props.defcon.form == "defcon" then umut_null_defvstem = com.apply_contraction(base_stem) else umut_null_defvstem = base_stem end null_defvstem = apply_reverse_u_mutation(umut_null_defvstem, props_unumut_form, not props.unumut.defaulted) elseif props.unimut and not props.unimut.form:find("^%-") then imut_nonvstem = base_stem local has_contraction = props.con and props.con.form == "con" nonvstem = apply_reverse_i_mutation(imut_nonvstem, base.unimutval, not has_contraction) stem = nonvstem if props_umut then umut_nonvstem = apply_u_mutation(nonvstem, props_umut.form, not props_umut.defaulted) end if base_vstem then error(("Don't currently know how to combine '%svstem:' with 'unimut' specs"):format( prefix == "pl_" and "pl" or "")) end if has_contraction then imut_vstem = com.apply_contraction(base_stem) else imut_vstem = base_stem end vstem = apply_reverse_i_mutation(imut_vstem, base.unimutval, "error if unmatchable") if props_umut then umut_vstem = apply_u_mutation(vstem, props_umut.form, not props_umut.defaulted) end if props.defcon and props.defcon.form == "defcon" then error("Don't currently know how to combine 'defcon' with 'unimut' specs") end base.need_imut = true elseif props_umut then stem = base_stem nonvstem = stem umut_nonvstem = apply_u_mutation(nonvstem, props_umut.form, not props_umut.defaulted) if base.need_imut then imut_nonvstem = apply_i_mutation(nonvstem, base.imutval) end vstem = base_vstem or base_stem if props.con and props.con.form == "con" then vstem = com.apply_contraction(vstem) end umut_vstem = apply_u_mutation(vstem, props_umut.form, not props_umut.defaulted) if base.need_imut then imut_vstem = apply_i_mutation(vstem, base.imutval) end if props.defcon and props.defcon.form == "defcon" then null_defvstem = com.apply_contraction(base_stem) else null_defvstem = base_stem end umut_null_defvstem = apply_u_mutation(null_defvstem, props_umut.form, not props_umut.defaulted) else -- Normally u-mutated forms should always be available, unless 'unumut' is in effect. error(("Internal error: Neither 'unumut' or 'umut' specified: %s"):format(dump(props))) end props[prefix .. "stem"] = stem if nonvstem ~= stem then props[prefix .. "nonvstem"] = nonvstem end if umut_nonvstem ~= nonvstem then -- For 'con' and 'defcon' below, footnotes can be placed on -con or -defcon so we have to check for -- those footnotes as well as checking for the vstem and such being different, so the -con and -defcon -- footnotes are still active. However, there's no such thing as -umut, and any time that there's an -- explicit umut variant given, umut_nonvstem will be different from nonvstem (otherwise an error will -- occur in apply_u_mutation), so we don't need this extra check here. if props_umut then umut_nonvstem = iut.combine_form_and_footnotes(umut_nonvstem, props_umut.footnotes) end props[prefix .. "umut_nonvstem"] = umut_nonvstem end if base.need_imut then -- imut footnotes handled specially below props[prefix .. "imut_nonvstem"] = imut_nonvstem end if vstem ~= stem or props.con and props.con.footnotes then -- See comment above for why we need to check for props.con.footnotes (basically, to handle footnotes on -- -con). if props.con then vstem = iut.combine_form_and_footnotes(vstem, props.con.footnotes) end props[prefix .. "vstem"] = vstem end if umut_vstem ~= vstem or props.con and props.con.footnotes then -- See comment above under `umut_nonvstem ~= nonvstem`. There's no -umut so whenever there's a specific -- umut variant with footnote, umut_vstem will be different from vstem so we don't need to check for -- `or props_umut and props_umut.footnotes` above. local footnotes = iut.combine_footnotes(props.con and props.con.footnotes or nil, props_umut and props_umut.footnotes or nil) umut_vstem = iut.combine_form_and_footnotes(umut_vstem, footnotes) props[prefix .. "umut_vstem"] = umut_vstem end if base.need_imut then -- imut footnotes handled specially below props[prefix .. "imut_vstem"] = imut_vstem end if null_defvstem ~= nonvstem or props.defcon and props.defcon.footnotes then -- See comment above for why we need to check for props.defcon.footnotes (basically, to handle footnotes -- on -defcon). if props.defcon then null_defvstem = iut.combine_form_and_footnotes(null_defvstem, props.defcon.footnotes) end props[prefix .. "null_defvstem"] = null_defvstem end if umut_null_defvstem ~= null_defvstem or props.defcon and props.defcon.footnotes then -- Analogous situation to the clause above that checks for `umut_vstem ~= vstem`. local footnotes = iut.combine_footnotes(props.defcon and props.defcon.footnotes or nil, props_umut and props_umut.footnotes or nil) umut_null_defvstem = iut.combine_form_and_footnotes(umut_null_defvstem, footnotes) props[prefix .. "umut_null_defvstem"] = umut_null_defvstem end end -- Do the j-infix, v-infix, imut, unimut and unumut properties. if props.j then props.jinfix = props.j.form == "j" and "j" or "" props.jinfix_footnotes = props.j.footnotes props.j = nil end if props.v then props.vinfix = props.v.form == "v" and "v" or "" props.vinfix_footnotes = props.v.footnotes props.v = nil end if props.imut then props.imut_footnotes = props.imut.footnotes props.imut = props.imut.form == "imut" and true or false end if props.unimut then props.unimut_footnotes = props.unimut.footnotes props.unimut = props.unimut.form == "unimut" and true or false end if props.unumut then props.unumut_footnotes = props.unumut.footnotes props.unumut = props.unumut.form end end end local function detect_indicator_spec(base) base.prop_sets = { {} } if base.adjspec then process_declnumber(base) synthesize_adj_lemma(base) elseif base.props.builtin then determine_builtin_props(base) else -- Replace # and ## in all overridable stems as well as all overrides. for _, stemkey in ipairs(overridable_stems) do base[stemkey] = com.replace_hashvals(base[stemkey], base.lemma) end map_all_overrides(base, function(formobj) formobj.form = com.replace_hashvals(formobj.form, base.lemma) end) expand_property_sets(base) if base.definiteness == "def" then synthesize_indefinite_lemma(base) end if base.number == "pl" then synthesize_singular_lemma(base) end determine_declension(base) determine_props(base) end end local function detect_all_indicator_specs(alternant_multiword_spec) -- Keep track of all genders seen in the singular and plural so we can determine whether to add the term to -- [[:Category:Icelandic nouns that change gender in the plural]]. FIXME: Is this needed for Icelandic? It's copied -- from Czech. alternant_multiword_spec.sg_genders = {} alternant_multiword_spec.pl_genders = {} iut.map_word_specs(alternant_multiword_spec, function(base) detect_indicator_spec(base) if base.number ~= "pl" then alternant_multiword_spec.sg_genders[base.actual_gender] = true end if base.number ~= "sg" then alternant_multiword_spec.pl_genders[base.actual_gender] = true end end) end local propagate_multiword_properties local function propagate_alternant_properties(alternant_spec, property, mixed_value, nouns_only) local seen_property for _, multiword_spec in ipairs(alternant_spec.alternants) do propagate_multiword_properties(multiword_spec, property, mixed_value, nouns_only) if seen_property == nil then seen_property = multiword_spec[property] elseif multiword_spec[property] and seen_property ~= multiword_spec[property] then seen_property = mixed_value end end alternant_spec[property] = seen_property end propagate_multiword_properties = function(multiword_spec, property, mixed_value, nouns_only) local seen_property = nil local last_seen_nounal_pos = 0 local word_specs = multiword_spec.alternant_or_word_specs or multiword_spec.word_specs for i = 1, #word_specs do local is_nounal if word_specs[i].alternants then propagate_alternant_properties(word_specs[i], property, mixed_value) is_nounal = not not word_specs[i][property] elseif nouns_only then is_nounal = is_regular_noun(word_specs[i]) else is_nounal = not not word_specs[i][property] end if is_nounal then if not word_specs[i][property] then error("Internal error: noun-type word spec without " .. property .. " set") end for j = last_seen_nounal_pos + 1, i - 1 do word_specs[j][property] = word_specs[j][property] or word_specs[i][property] end last_seen_nounal_pos = i if seen_property == nil then seen_property = word_specs[i][property] elseif seen_property ~= word_specs[i][property] then seen_property = mixed_value end end end if last_seen_nounal_pos > 0 then for i = last_seen_nounal_pos + 1, #word_specs do word_specs[i][property] = word_specs[i][property] or word_specs[last_seen_nounal_pos][property] end end multiword_spec[property] = seen_property end local function propagate_properties_downward(alternant_multiword_spec, property, default_propval) local function set_and_fetch(obj, default) local retval if obj[property] then retval = obj[property] else obj[property] = default retval = default end if not obj["actual_" .. property] then obj["actual_" .. property] = retval end return retval end local propval1 = set_and_fetch(alternant_multiword_spec, default_propval) for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do local propval2 = set_and_fetch(alternant_or_word_spec, propval1) if alternant_or_word_spec.alternants then for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do local propval3 = set_and_fetch(multiword_spec, propval2) for _, word_spec in ipairs(multiword_spec.word_specs) do local propval4 = set_and_fetch(word_spec, propval3) if propval4 == "mixed" then -- FIXME, use clearer error message. error("Attempt to assign mixed " .. property .. " to word") end set_and_fetch(word_spec, propval4) end end else if propval2 == "mixed" then -- FIXME, use clearer error message. error("Attempt to assign mixed " .. property .. " to word") end set_and_fetch(alternant_or_word_spec, propval2) end end end --[=[ Propagate `property` (one of "gender", "number" or "definiteness") from nouns to adjacent adjectives. We proceed as follows: 1. We assume the properties in question are already set on all nouns. This should happen in set_defaults_and_check_bad_indicators(). 2. We first propagate properties upwards and sideways. We recurse downwards from the top. When we encounter a multiword spec, we proceed left to right looking for a noun. When we find a noun, we fetch its property (recursing if the noun is an alternant), and propagate it to any adjectives to its left, up to the next noun to the left. When we have processed the last noun, we also propagate its property value to any adjectives to the right (to handle e.g. [[svefninn langi]] "the long sleep", where the adjective [[langi]] should inherit the 'masculine', 'singular' and 'definite' properties of [[svefninn]]). Finally, we set the property value for the multiword spec itself by combining all the non-nil properties of the individual elements. If all non-nil properties have the same value, the result is that value, otherwise it is `mixed_value` (which is "mixed" for gender, but "both" for number and "bothdef" for definiteness). 3. When we encounter an alternant spec in this process, we recursively process each alternant (which is a multiword spec) using the previous step, and combine any non-nil properties we encounter the same way as for multiword specs. 4. The effect of steps 2 and 3 is to set the property of each alternant and multiword spec based on its children or its neighbors. ]=] local function propagate_properties(alternant_multiword_spec, property, default_propval, mixed_value) propagate_multiword_properties(alternant_multiword_spec, property, mixed_value, "nouns only") propagate_multiword_properties(alternant_multiword_spec, property, mixed_value, false) propagate_properties_downward(alternant_multiword_spec, property, default_propval) end local function determine_noun_status(alternant_multiword_spec) for i, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do if alternant_or_word_spec.alternants then local is_noun = false for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do for j, word_spec in ipairs(multiword_spec.word_specs) do if is_regular_noun(word_spec) then multiword_spec.first_noun = j is_noun = true break end end end if is_noun then alternant_multiword_spec.first_noun = i end elseif is_regular_noun(alternant_or_word_spec) then alternant_multiword_spec.first_noun = i return end end end -- Set the part of speech based on properties of the individual words. local function set_pos(alternant_multiword_spec) if not alternant_multiword_spec.pos then if alternant_multiword_spec.saw_builtin and not alternant_multiword_spec.saw_non_builtin then alternant_multiword_spec.pos = "pronoun" else alternant_multiword_spec.pos = "noun" end end end local function normalize_all_lemmas(alternant_multiword_spec) iut.map_word_specs(alternant_multiword_spec, function(base) local lemma = base.orig_lemma_no_links base.actual_lemma = lemma base.lemma = base.decllemma or lemma base.source_template = alternant_multiword_spec.source_template end) end local function decline_noun(base) for _, props in ipairs(base.prop_sets) do if not decls[base.decl] then error("Internal error: Unrecognized declension type '" .. base.decl .. "'") end decls[base.decl](base, props) end handle_derived_slots_and_overrides(base) local function copy(from_slot, to_slot) base.forms["ind_" .. to_slot] = base.forms["ind_" .. from_slot] base.forms["def_" .. to_slot] = base.forms["def_" .. from_slot] end if base.actual_number ~= base.number then local source_num = base.number == "sg" and "_s" or "_p" local dest_num = base.number == "sg" and "_p" or "_s" for _, case in ipairs(cases) do copy(case .. source_num, case .. dest_num) copy("nom" .. source_num .. "_linked", "nom" .. dest_num .. "_linked") end if base.actual_number ~= "both" then local erase_num = base.actual_number == "sg" and "_p" or "_s" for _, case in ipairs(cases) do base.forms["ind_" .. case .. erase_num] = nil base.forms["def_" .. case .. erase_num] = nil end base.forms["ind_nom" .. erase_num .. "_linked"] = nil base.forms["def_nom" .. erase_num .. "_linked"] = nil end end process_addnote_specs(base) end -- Compute the categories to add the noun to, as well as the annotation to display in the -- declension title bar. We combine the code to do these functions as both categories and -- title bar contain similar information. local function compute_categories_and_annotation(alternant_multiword_spec) local all_cats = {} local plpos = require(en_utilities_module).pluralize(alternant_multiword_spec.pos) local function inscat(cattype) m_table.insertIfNot(all_cats, "Icelandic " .. cattype) end local function inscat_noun(cattype) if plpos == "nouns" then inscat(cattype) end end if alternant_multiword_spec.saw_indecl and not alternant_multiword_spec.saw_non_indecl then inscat("indeclinable " .. plpos) end if alternant_multiword_spec.saw_unknown_decl and not alternant_multiword_spec.saw_non_unknown_decl then inscat(plpos .. " with unknown declension") end if alternant_multiword_spec.actual_number == "sg" then inscat_noun("uncountable nouns") elseif alternant_multiword_spec.actual_number == "pl" then inscat_noun("pluralia tantum") end local annparts = {} local irregs = {} local genderspecs = {} local stemspecs = {} local scrape_chains = {} local function insann(txt, joiner) if joiner and annparts[1] then table.insert(annparts, joiner) end table.insert(annparts, txt) end local function do_word_spec(base) local actual_gender = gender_code_to_desc[base.actual_gender] local declined_gender = gender_code_to_desc[base.gender] local gender if actual_gender ~= declined_gender then gender = ("%s (declined as %s)"):format(actual_gender, declined_gender) inscat_noun("nouns with actual gender different from declined gender") else gender = actual_gender end if gender then m_table.insertIfNot(genderspecs, gender) end for _, props in ipairs(base.prop_sets) do -- User-specified 'decllemma:' indicates irregular stem. if base.decllemma then m_table.insertIfNot(irregs, "irreg-stem") inscat_noun("nouns with irregular stem") end m_table.insertIfNot(stemspecs, props.stem) end end local key_entry = alternant_multiword_spec.first_noun or 1 if #alternant_multiword_spec.alternant_or_word_specs >= key_entry then local alternant_or_word_spec = alternant_multiword_spec.alternant_or_word_specs[key_entry] if alternant_or_word_spec.alternants then for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do key_entry = multiword_spec.first_noun or 1 if #multiword_spec.word_specs >= key_entry then do_word_spec(multiword_spec.word_specs[key_entry]) end end else do_word_spec(alternant_or_word_spec) end end iut.map_word_specs(alternant_multiword_spec, function(base) if base.scrape_chain[1] then local linked_scrape_chain = {} for _, element in ipairs(base.scrape_chain) do table.insert(linked_scrape_chain, ("[[%s]]"):format(element)) end m_table.insertIfNot(scrape_chains, table.concat(linked_scrape_chain, " -> ")) end end) if alternant_multiword_spec.actual_number == "sg" or alternant_multiword_spec.actual_number == "pl" then -- not "both" or "none" (for [[sebe]]) insann(alternant_multiword_spec.actual_number .. "-only", " ") end if #genderspecs > 0 then insann(table.concat(genderspecs, " // "), " ") end if #irregs > 0 then insann(table.concat(irregs, " // "), " ") end if #scrape_chains > 0 then insann(("based on %s"):format(m_table.serialCommaJoin(scrape_chains)), ", ") inscat(plpos .. " declined using scraped base declensions") end alternant_multiword_spec.annotation = table.concat(annparts) if #stemspecs > 1 then inscat_noun("nouns with multiple stems") end if alternant_multiword_spec.actual_number == "both" and not m_table.deepEquals(alternant_multiword_spec.sg_genders, alternant_multiword_spec.pl_genders) then inscat_noun("nouns that change gender in the plural") end alternant_multiword_spec.categories = all_cats end local function show_forms(alternant_multiword_spec) local lemmas = {} local max_num_words = 1 for _, slot in ipairs(potential_lemma_slots) do if alternant_multiword_spec.forms[slot] then for _, formobj in ipairs(alternant_multiword_spec.forms[slot]) do table.insert(lemmas, formobj) local no_affix_form = formobj.form:gsub("^%-", ""):gsub("%-$", "") local num_words = #(rsplit(no_affix_form, "[ -]+")) max_num_words = math.max(max_num_words, num_words) end break end end alternant_multiword_spec.max_num_words = max_num_words local props = { lemmas = lemmas, slot_list = alternant_multiword_spec.noun_slots, lang = lang, } iut.show_forms(alternant_multiword_spec.forms, props) end local function make_table(alternant_multiword_spec) local forms = alternant_multiword_spec.forms local frame = mw.getCurrentFrame() local function template_prelude() return m_inflection_table.make_top{ title = '{title}{annotation}', palette = 'blue', tall = 'yes', } end local function template_postlude() return m_inflection_table.make_bottom{ notes = '{footnote}', } end local table_spec_both = template_prelude() .. [=[ ! rowspan="2" | ! colspan="2" | singular ! colspan="2" | plural |- ! class="secondary" | indefinite ! class="secondary" | definite ! class="secondary" | indefinite ! class="secondary" | definite |- ! nominative | {ind_nom_s} | {def_nom_s} | {ind_nom_p} | {def_nom_p} |- ! accusative | {ind_acc_s} | {def_acc_s} | {ind_acc_p} | {def_acc_p} |- ! dative | {ind_dat_s} | {def_dat_s} | {ind_dat_p} | {def_dat_p} |- ! genitive | {ind_gen_s} | {def_gen_s} | {ind_gen_p} | {def_gen_p} ]=] .. template_postlude() local function get_table_spec_one_number(number, numcode) local table_spec_one_number = [=[ ! rowspan="2" | ! colspan="2" | NUMBER |- ! class="secondary" | indefinite ! class="secondary" | definite |- ! nominative | {ind_nom_NUM} | {def_nom_NUM} |- ! accusative | {ind_acc_NUM} | {def_acc_NUM} |- ! dative | {ind_dat_NUM} | {def_dat_NUM} |- ! genitive | {ind_gen_NUM} | {def_gen_NUM} ]=] return template_prelude() .. table_spec_one_number:gsub("NUMBER", number):gsub("NUM", numcode) .. template_postlude() end local function get_table_spec_one_number_one_def(number, numcode, definiteness, defcode) local table_spec_one_number_one_def = [=[ ! colspan="2" | DEFINITENESS NUMBER |- ! nominative | {DEF_nom_NUM} |- ! accusative | {DEF_acc_NUM} |- ! dative | {DEF_dat_NUM} |- ! genitive | {DEF_gen_NUM} ]=] return template_prelude() .. (table_spec_one_number_one_def:gsub("NUMBER", number):gsub("NUM", numcode) :gsub("DEFINITENESS", definiteness):gsub("DEF", defcode)) .. template_postlude() end if alternant_multiword_spec.title then forms.title = alternant_multiword_spec.title else forms.title = 'Declension of <i lang="is">' .. forms.lemma .. '</i>' end local annotation = alternant_multiword_spec.annotation if annotation == "" then forms.annotation = "" else forms.annotation = " (<span style=\"font-size: smaller;\">" .. annotation .. "</span>)" end local number, numcode if alternant_multiword_spec.actual_number == "sg" then number, numcode = "singular", "s" elseif alternant_multiword_spec.actual_number == "pl" then number, numcode = "plural", "p" elseif alternant_multiword_spec.actual_number == "none" then -- used for [[sebe]] -- FIXME: Update for Icelandic number, numcode = "", "s" end local definiteness, defcode if alternant_multiword_spec.definiteness == "indef" then definiteness, defcode = "indefinite", "ind" elseif alternant_multiword_spec.definiteness == "def" then definiteness, defcode = "definite", "def" elseif alternant_multiword_spec.definiteness == "none" then definiteness, defcode = "", "ind" end local table_spec = alternant_multiword_spec.actual_number ~= "both" and alternant_multiword_spec.definiteness ~= "bothdef" and get_table_spec_one_number_one_def(number, numcode, definiteness, defcode) or alternant_multiword_spec.actual_number == "both" and table_spec_both or get_table_spec_one_number(number, numcode) return m_string_utilities.format(table_spec, forms) end local function compute_headword_genders(alternant_multiword_spec) local genders = {} local number if alternant_multiword_spec.actual_number == "pl" then number = "-p" else number = "" end iut.map_word_specs(alternant_multiword_spec, function(base) if base.actual_gender ~= "none" then m_table.insertIfNot(genders, base.actual_gender .. number) end end) return genders end -- Externally callable function to parse and decline a noun given user-specified arguments and the argument spec -- `argspec` (specified because the user may give multiple such specs). Return value is ALTERNANT_MULTIWORD_SPEC, an -- object where the declined forms are in `ALTERNANT_MULTIWORD_SPEC.forms` for each slot. If there are no values for a -- slot, the slot key will be missing. The value for a given slot is a list of objects {form=FORM, footnotes=FOOTNOTES}. function export.do_generate_forms(args, argspec, source_template) local pagename = args.pagename or mw.loadData("Module:headword/data").pagename local parse_props = { parse_indicator_spec = function(angle_bracket_spec, lemma) return parse_indicator_spec(angle_bracket_spec, lemma, pagename) end, angle_brackets_omittable = true, allow_blank_lemma = true, } local alternant_multiword_spec = iut.parse_inflected_text(argspec, parse_props) alternant_multiword_spec.title = args.title alternant_multiword_spec.pos = args.pos alternant_multiword_spec.args = args alternant_multiword_spec.source_template = source_template local scrape_errors = {} iut.map_word_specs(alternant_multiword_spec, function(base) if base.scrape_error then table.insert(scrape_errors, base.scrape_error) end end) if scrape_errors[1] then alternant_multiword_spec.scrape_errors = scrape_errors else normalize_all_lemmas(alternant_multiword_spec) set_all_defaults_and_check_bad_indicators(alternant_multiword_spec) -- These need to happen before detect_all_indicator_specs() so that adjectives get their genders and number -- set appropriately, which are needed to correctly synthesize the adjective lemma. propagate_properties(alternant_multiword_spec, "number", "both", "both") -- FIXME, the default value (third param) used to be 'm' with a comment indicating that this applied only to -- plural adjectives, where it didn't matter; but in Icelandic, plural adjectives are distinguished for gender. -- Make sure 'mixed' works. propagate_properties(alternant_multiword_spec, "gender", "mixed", "mixed") propagate_properties(alternant_multiword_spec, "definiteness", "bothdef", "bothdef") detect_all_indicator_specs(alternant_multiword_spec) -- Propagate 'actual_number' after calling detect_all_indicator_specs(), which sets 'actual_number' for -- adjectives. propagate_properties(alternant_multiword_spec, "actual_number", "both", "both") determine_noun_status(alternant_multiword_spec) set_pos(alternant_multiword_spec) alternant_multiword_spec.noun_slots = get_noun_slots(alternant_multiword_spec) local inflect_props = { skip_slot = function(slot) return skip_slot(alternant_multiword_spec.actual_number, alternant_multiword_spec.definiteness, slot) end, slot_list = alternant_multiword_spec.noun_slots, inflect_word_spec = decline_noun, } iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props) compute_categories_and_annotation(alternant_multiword_spec) alternant_multiword_spec.genders = compute_headword_genders(alternant_multiword_spec) end if args.json then alternant_multiword_spec.args = nil return require("Module:JSON").toJSON(alternant_multiword_spec) end return alternant_multiword_spec end -- Entry point for {{is-ndecl}}. Template-callable function to parse and decline a noun given -- user-specified arguments and generate a displayable table of the declined forms. function export.show(frame) local parent_args = frame:getParent().args local params = { [1] = { required = true, list = true, default = "akur<m.#>" }, deriv = { list = true }, id = {}, pos = {}, title = {}, pagename = {}, json = { type = "boolean" }, } local args = m_para.process(parent_args, params) local alternant_multiword_specs = {} for i, argspec in ipairs(args[1]) do alternant_multiword_specs[i] = export.do_generate_forms(args, argspec, "is-ndecl") end if args.json then -- JSON return value if #args[1] == 1 then return alternant_multiword_specs[1] else return alternant_multiword_specs end end local parts = {} local function ins(txt) table.insert(parts, txt) end for _, alternant_multiword_spec in ipairs(alternant_multiword_specs) do if not alternant_multiword_spec.scrape_errors then show_forms(alternant_multiword_spec) end if alternant_multiword_spec.header then ins(("'''%s:'''\n"):format(alternant_multiword_spec.header)) end if alternant_multiword_spec.q then ins(("''%s''\n"):format(alternant_multiword_spec.q)) end local categories if alternant_multiword_spec.scrape_errors then local errmsgs = {} for _, scrape_error in ipairs(alternant_multiword_spec.scrape_errors) do table.insert(errmsgs, '<span style="font-weight: bold; color: var(--wikt-palette-red,#CC2200);">' .. scrape_error .. "</span>") end -- Surround the messages with a <div> because the table normally does that, and we want to ensure -- similar formatting with respect to newlines. ins("<div>" .. table.concat(errmsgs, "<br />") .. "</div>") categories = { "Icelandic scraping errors in Template:is-ndecl" } else ins(make_table(alternant_multiword_spec)) categories = alternant_multiword_spec.categories end ins(require("Module:utilities").format_categories(categories, lang, nil, nil, force_cat)) end return table.concat(parts) end return export 8iow73f0xqda3hqkyuxbktr4cf9dulq 393947 393944 2026-10-08T19:30:53Z Ow! That Hurts! 8952 393947 Scribunto text/plain local export = {} --[=[ Authorship: Ben Wing <benwing2> ]=] --[=[ TERMINOLOGY: -- "slot" = A particular combination of case/number. Example slot names for nouns are "acc_s" (accusative singular) and "gen_p" (genitive plural). Each slot is filled with zero or more forms. -- "form" = The declined Icelandic form representing the value of a given slot. -- "lemma" = The dictionary form of a given Icelandic term. Generally the nominative singular, or nominative plural of plural-only nouns, but may occasionally be another form if the nominative is missing. ]=] --[=[ FIXME: 1. Support 'plstem' overrides. [DONE] 2. Support definite lemmas such as [[Bandaríkin]] "the United States". [DONE] 3. Support adjectivally-declined terms. [DONE PARTIALLY] 4. Support @ for built-in irregular lemmas. [DONE; SINCE REPLACED WITH SCRAPING SPECS] 5. Somehow if the user specifies v-infix, it should prevent default unumut from happening in strong feminines. [DONE] 6. Def acc pl should ignore -u ending in indef acc pl. [DONE] 7. Footnotes on omitted forms should be possible. 8. Remove setting of override on pl when processing plural-only terms in synthesize_singular_lemma(); interferes with decllemma in [[dyr]]. But then need to fix handling of masculine accusative plural. [DONE] 9. Rationalize conventions used in u-mutation types. [DONE] 10. Compute defaulted number and definiteness early so it's usable when merging built-in and user-specified specs. [DONE] 11. Support multiple declension specs. [DONE] 12. Support dark mode. [DONE] 13. Support scraping declension specs. [DONE] 14. Support @-d etc. for suffix scraping. [DONE] 15. Include scraped base nouns in title annotation. [DONE] 16. Support @@ for self-scraping. [DONE IN [[Module:gmq-headword]]] 17. Support scraping multiple declension specs; e.g. [[fræði]] is declared with 'n.pl|f.sg' and [[hljómfræði]] would like to use '@f' but you get an error. ]=] local lang = require("Module:tungumál").getByCode("is") local require_when_needed = require("Module:utilities/require when needed") local m_table = require("Module:table") local m_links = require("Module:links") local m_string_utilities = require("Module:string utilities") local iut = require("Module:inflection utilities") local put = require("Module:parse utilities") local m_para = require("Module:parameters") local com = require("Module:is-common") local m_inflection_table = require("Module:inflection-table") local m_is_adjective = require_when_needed("Module:is-adjective") local en_utilities_module = "Module:en-utilities" local u = mw.ustring.char local rsplit = mw.text.split local rfind = mw.ustring.find local rmatch = mw.ustring.match local rsubn = mw.ustring.gsub local ulen = mw.ustring.len local usub = mw.ustring.sub local uupper = mw.ustring.upper local ulower = mw.ustring.lower local dump = mw.dumpObject local force_cat = false -- set to true to make categories appear in non-mainspace pages, for testing local SUB_ESCAPED_PERIOD = u(0xFFF0) local SUB_ESCAPED_COMMA = u(0xFFF1) -- version of rsubn() that discards all but the first return value local function rsub(term, foo, bar) local retval = rsubn(term, foo, bar) return retval end -- version of rsubn() that returns a 2nd argument boolean indicating whether -- a substitution was made. local function rsubb(term, foo, bar) local retval, nsubs = rsubn(term, foo, bar) return retval, nsubs > 0 end local function track(track_id) require("Module:debug/track")("is-noun/" .. track_id) return true end local potential_lemma_slots = { "ind_nom_s", "ind_nom_p", "def_nom_s", "def_nom_p", "ind_acc_s", -- for [[sig]] } local cases = { "nom", "acc", "dat", "gen", } local case_set = m_table.listToSet(cases) local overridable_stems = { "stem", "vstem", "plstem", "plvstem", "imutval", "unimutval", } local overridable_stem_set = m_table.listToSet(overridable_stems) local control_specs = { "umut", "imut", "unumut", "unimut", "con", "defcon", "j", "v", } local control_spec_set = m_table.listToSet(control_specs) local clitic_articles = { m = { nom_s = "inn", acc_s = "inn", dat_s = "num", gen_s = "ins", nom_p = "nir", acc_p = "na", dat_p = "num", gen_p = "nna", }, f = { nom_s = "in", acc_s = "ina", dat_s = "inni", gen_s = "innar", nom_p = "nar", acc_p = "nar", dat_p = "num", gen_p = "nna", }, n = { nom_s = "ið", acc_s = "ið", dat_s = "nu", gen_s = "ins", nom_p = "in", acc_p = "in", dat_p = "num", gen_p = "nna", }, } local gender_code_to_desc = { m = "masculine", f = "feminine", n = "neuter", none = nil, } local number_code_to_desc = { sg = "singular", pl = "plural", both = "both numbers", none = nil, } local definiteness_code_to_desc = { indef = "indefinite-only", def = "definite-only", bothdef = "indefinite and definite", none = nil, } local function get_noun_slots(alternant_multiword_spec) local noun_slots_list = {} for _, case in ipairs(cases) do for _, num in ipairs { "s", "p" } do for _, def in ipairs { "ind", "def" } do local slot = ("%s_%s_%s"):format(def, case, num) local accel = ("%s|%s"):format(def == "ind" and "indef" or def, case) if alternant_multiword_spec.actual_number == "both" then accel = accel .. "|" .. num end table.insert(noun_slots_list, { slot, accel }) end end end for _, potential_lemma_slot in ipairs(potential_lemma_slots) do table.insert(noun_slots_list, { potential_lemma_slot .. "_linked", "-" }) end return noun_slots_list end local function generate_list_of_possibilities_for_err(list) local quoted_list = {} for _, item in pairs(list) do if item == "" then item = "<nowiki />" end table.insert(quoted_list, "'" .. item .. "'") end table.sort(quoted_list) return mw.text.listToText(quoted_list) end local function skip_slot(number, definiteness, slot) return number == "sg" and slot:find("_p$") or number == "pl" and slot:find("_s$") or definiteness == "def" and slot:find("^ind_") or (definiteness == "indef" or definiteness == "none") and slot:find("^def_") end local function apply_i_mutation(stem, newv) return com.apply_i_mutation(stem, newv, "exclude final -ur", "error if unmatchable") end local function apply_reverse_i_mutation(stem, newv, error_if_unmatchable) return com.apply_reverse_i_mutation(stem, newv, "exclude final -ur", error_if_unmatchable) end local function apply_u_mutation(stem, typ, error_if_unmatchable) return com.apply_u_mutation(stem, typ, "exclude final -ur", error_if_unmatchable) end local function apply_reverse_u_mutation(stem, typ, error_if_unmatchable) return com.apply_reverse_u_mutation(stem, typ, "exclude final -ur", error_if_unmatchable) end --[=[ Create an empty `base` object for holding the result of parsing and later the generated forms. The object is of the form { -- Original lemma as directly given by the user or taken from the pagename. orig_lemma = "ORIGINAL-LEMMA", -- Same as `orig_lemma` but with links removed. orig_lemma_no_links = "ORIGINAL-LEMMA-NO-LINKS", -- Originally the same as `orig_lemma_no_links`, but if the term is a plural-only noun, this will be the corresponding -- singular lemma, and if the term is an adjective form, this will be the corresponding lemma (strong nominative -- masculine singular form). lemma = "LEMMA", -- Generated per-slot forms. After calling `inflect_multiword_or_alternant_multiword_spec`, the forms will be filled -- in with the format as given below, where the value of each slot is a form object. After calling `show_forms`, the -- value of each slot will be a formatted string listing all of the forms of that slot, or "—" if there are none. forms = { SLOT = { { form = "FORM", footnotes = nil or {"FOOTNOTE", "FOOTNOTE", ...}, }, ... }, ... }, -- Specs for control groups as specified by the user. CONTROL_GROUP is as below and CONTROL_SPEC is -- {form = "FORM", footnotes = nil or {"FOOTNOTE", "FOOTNOTE", ...}, defaulted = BOOLEAN}, where FORM is as specified -- by the user (e.g. "uUmut", "-unumut") or set as a default by the code (in which case `defaulted` will be set to -- true for control groups "umut" and "unumut"). The control groups are as follows: -- * umut (u-mutation); -- * imut (i-mutation); -- * unumut (reverse u-mutation); -- * unimut (reverse i-mutation); -- * con (stem contraction before vowel-initial endings); -- * defcon (stem contraction before vowel-initial definite clitics when the ending itself is null); -- * j (j-infix before vowel-initial endings not beginning with an i); -- * v (v-infix before vowel-initial endings). CONTROL_GROUP = { CONTROL_SPEC, CONTROL_SPEC, ... }, -- Property sets containing computed stems, one per each combination of control group values. Described in more detail -- below. prop_sets = { PROPSET, -- see below ..., }, -- Per-slot overrides, which override forms generated by the auto-determined or specified declension pattern. SLOT is -- the actual name of the slot, normally without the definiteness prefix, such as "dat_s" (NOT the slot name as -- specified by the user, which would be just "dat" for "dat_s") and OVERRIDE is of the form -- {indef = {FORMOBJ, FORMOBJ, ...}, def = nil or false or {FORMOBJ, FORMOBJ, ...}}, where FORMOBJ is of the form -- {form = FORM, footnotes = FOOTNOTES} as in the `forms` table ("-" means to suppress the slot entirely and is -- signaled by "--" as the user-specified form value; normally FORM values are endings, but a value preceded by ! -- means it's a full form rather than an ending; in such forms you can use # to indicate the lemma and ## to indicate -- the lemma minus -ur or -r, as with stems); `indef` means the override(s) of the indefinite variant of the slot and -- is specified by the user before a slash; `def` means the override(s) of the definite variant of the slot and come -- after a slash, and `false` for either means that the user left the value before or after the slash completely -- blank, meaning not to override the indefinite or definite forms. Sometimes the slot itself has def_ in it; this -- happens when the user preceded the slot spec by 'def', e.g. 'defdat' or 'defgenpl'. overrides = { SLOT = OVERRIDE, SLOT = OVERRIDE, ... }, -- Overrides for the genitive singular, specified after a comma after the gender. OVERRIDE is in the same format as -- above. gens = nil or OVERRIDE, -- Overrides for the nominative and accusative plural, specified after a comma after the gender. OVERRIDE is in the -- same format as above. The actual values given are for the nominative plural, and the accusative plural is derived -- automatically from these values. pls = nil or OVERRIDE, -- "sg", "pl", "both" or "none" (for certain pronouns); may be missing and if so is defaulted number = "NUMBER", -- "m", "f", "n" or "none" (for certain pronouns); always specified by the user gender = "GENDER", -- "def", "indef", "bothdef" or "none" (for pronouns); may be missing and if so is defaulted definiteness = "DEFINITENESS", -- decline like the specified lemma decllemma = nil or "DECLLEMMA", -- decline like the specified gender declgender = nil or "DECLGENDER", -- decline like the specified number declnumber = nil or "DECLNUMBER", -- override the stem; may have # (= lemma) or ## (= lemma minus -ur or -r) stem = nil or "STEM", -- override the stem used before vowel-initial endings; same format as `stem` vstem = nil or "STEM", -- override the plural stem; same format as `stem` plstem = nil or "STEM", -- override the plural stem used before vowel-initial endings; same format as `stem` plvstem = nil or "STEM", -- decline like an adjective; will be present if the user gave a spec starting with 'adj' adjspec = { -- User explicitly specified the lemma using a colon + lemma. lemma = nil or LEMMA -- User gave a one-part or two-part substitution spec such as 'tvöfalt<adj/dur>' or 'ryðfrítt<adj/tt/r>'. subspec = nil or { from = nil or FROM, to = TO, }, }, -- misc Boolean properties: -- * "proper" (a lowercase noun that behaves like a proper noun, i.e. defaults to no plural or definite forms); -- * "common" (a capitalized noun that behaves like a common noun, i.e. defaults to plural and definite forms); -- * "dem" (a demonym, i.e. a capitalized noun such as [[Svisslendingur]] "Swiss person" that behaves like a common -- noun; currently behaves like "common"); -- * "builtin" (for built-in terms such as pronouns); -- * "indecl" (noun is indeclinable); -- * "decl?" (declension is unknown); -- * "iending" (a definite-only noun whose lemma ends in -i, which is elided before a definite clitic beginning with -- i-); -- * "rstem" (an r-stem like [[bróðir]] "brother" or [[dóttir]] "daughter"); -- * "já" (a neuter in -é whose stem alternates with -já, such as [[tré]] "tree" and [[hné]]/[[kné]] "knee"); -- * "weak" (the noun should decline like an ordinary weak noun; used in the declension of [[fjandi]] to disable the -- special -ndi declension); -- * "linkasis" (when linking definite-only and plural-only lemmas in the headword, link as-is instead of -- linking the singular indefinite version); props = { PROP = true, PROP = true, ... }, -- Alternant-level footnotes, specified using `.[footnote]`, i.e. a footnote by itself. footnotes = nil or {"FOOTNOTE", "FOOTNOTE", ...}, -- ADDNOTE_SPEC is {slot_specs = {"SPEC", "SPEC", ...}, footnotes = {"FOOTNOTE", "FOOTNOTE", ...}}; SPEC is a Lua -- pattern matching slots (anchored on both sides) and FOOTNOTE is a footnote to add to those slots. addnote_specs = { ADDNOTE_SPEC, ADDNOTE_SPEC, ... }, } There is one PROPSET (property set) for each combination of control specs; in the lower limit, there is a single property set. There may be more than one property set e.g. if the user specified 'umut,uUmut' or '-j,j' or '-imut,imut' or some combination of these. The properties in a given property set specify the values themselves of each control group, as well as stems (derived from the control specs) that are used to construct the various forms and populate the slots in `forms` with these values. The information found in the property sets cannot be stored in `base` because it depends on a particular combination of control specs, of which there may be more than one (see above). The decline_noun() function iterates over all property sets and calls the appropriate declension function on each one in turn, which adds forms to each slot in `base.forms`, automatically deduplicating. The properties in each property set are: * Control specs: These are copied from the control specs at the base level. The key is one of the possible control groups ("umut", "imut", "con", etc.), but the value is a single form object {form = "FORM", footnotes = nil or {"FOOTNOTE", "FOOTNOTE", ...}}. These are set by expand_property_sets(). * Stems (each stem is either a string or a form object; stems in general may be missing, i.e. nil, unless otherwise specified, and default to more general variants): ** `stem`: The basic stem. Always set. May be overridden by more specific variants. ** `nonvstem`: The stem used when the ending is null or starts with a consonant, unless overridden by a more specific variant. Defaults to `stem`. Not currently used, but could be if e.g. a user stem override `nonvstem:...` were supported. ** `umut_nonvstem`: The stem used when the ending is null or starts with a consonant and u-mutation is in effect, unless overridden by a more specific variant. Defaults to `nonvstem`. Will only be present when the result of u-mutation is different from the stem to which u-mutation is applied. (In this case, it will be present even if `nonvstem` is missing, because there is no generic `umut_stem`.) ** `imut_nonvstem`: The stem used when the ending is null or starts with a consonant and i-mutation is in effect. If i-mutation is in effect, this should always be specified (otherwise an internal error will occur); hence it has no default. Note that i-mutation is only in effect when either (a) `imut` or `unimut` was specified; (b) a user-specified override is given that begins with a single ^ (indicating i-mutation); or (c) a declension type is in effect that contains default endings beginning with a single ^ (examples are `f-long-vowel` for lemmas in -ó and `f-long-umlaut-vowel-r`). Note also that this will be present even if `nonvstem` is missing, because there is no generic `imut_stem`. ** `vstem`: The stem used when the ending starts with a vowel, unless overridden by a more specific variant. Defaults to `stem`. Will be specified when contraction is in effect or the user specified `vstem:...`. ** `umut_vstem`: The stem(s) used when the ending starts with a vowel and u-mutation is in effect. Defaults to `vstem`. Note that u-mutation applies to the contracted stem if both u-mutation and contraction are in effect. Will only be present when the result of u-mutation is different from the stem to which u-mutation is applied. (In this case, it will be present even if `vstem` is missing, because there is no generic `umut_stem`.) ** `imut_vstem`: The stem(s) used when the ending starts with a vowel and i-mutation is in effect. If i-mutation is in effect, this should always be specified (otherwise an internal error will occur); hence it has no default. Note that i-mutation applies to the contracted stem if both i-mutation and contraction are in effect. See `imut_nonvstem` for comments on when this stem will be present. ** `null_defvstem`: The stem(s) used when the ending is null and is followed by a definite ending that begins with a vowel, unless overridden by a more specific variant. Defaults to `nonvstem`. This is normally set when `defcon` is specified. ** `umut_null_defvstem`: The stem(s) used when the ending is null and is followed by a definite ending that begins with a vowel, and u-mutation is in effect. Defaults to `null_defvstem`. This is normally set when `defcon` is specified and u-mutation is needed, as in the nom/acc pl of neuter [[mastur]] "mast". Will only be present when the result of u-mutation is different from the stem to which u-mutation is applied. ** `pl_stem`: The basic stem used for plural inflections. Only set when `plstem:...` is specified by the user. If this is set, the alternative plural-specific stem variants are used, where each of the above stems has a plural-specific counterpart, and the identical algorithms and fallbacks are used to determine the correct stem. ** `pl_nonvstem`, `pl_umut_nonvstem`, `pl_imut_nonvstem`, `pl_vstem`, `pl_umut_vstem`, `pl_imut_vstem`, `pl_null_defvstem`, `pl_umut_null_defvstem`: Plural-specific counterparts of the above stems. See the comment under `pl_stem` for when these are used. * Other properties: ** `jinfix`: If present, either "" or "j". Inserted between the stem and ending when the ending begins with a vowel other than "i". Note that j-infixes don't apply to ending overrides. ** `jinfix_footnotes`: Footnotes to attach to forms where j-infixing is possible (even if it's not present). ** `vinfix`: If present, either "" or "v". Inserted between the stem and ending when the ending begins with a vowel. Note that v-infixes don't apply to ending overrides. `jinfix` and `vinfix` cannot both be specified. ** `vinfix_footnotes`: Footnotes to attach to forms where v-infixing is possible (even if it's not present). ** `imut`: If specified (i.e. not nil), either true or false. If specified, there may be associated footnotes in `imut_footnotes`. If true, i-mutation and associated footnotes are in effect before endings starting with "i". If false, associated footnotes still apply before endings starting with "i". Note that i-mutation is also in effect if the ending has ^ prepended, but the associated footnotes don't apply here. ** `imut_footnotes`: See `imut`. ** `unumut`: If specified (i.e. not nil), the type of un-u-mutation requested (either "unumut" or a variant, or the negation of the same using "-unumut" or a variant for no un-u-mutation; "unumut" and variants differ in which slots any associated footnote are placed). If specified, there may be associated footnotes in `unumut_footnotes`. If "unumut" itself, u-mutation is in effect *except* before an ending that starts with an "a" or "i" (unless i-mutation is in effect, which takes precedence). If any other variant, the rules are different: when masculine, u-mutation is in effect *except* in the gen sg and pl (examples are [[söfnuður]] "congregation" and [[mánuður]] "month"); when feminine, u-mutation is in effect except in the nom/acc/gen pl (examples are [[verslun]] "trade, business; store, shop" and [[kvörtun]] "complaint"). When u-mutation is *not* in effect, and i-mutation is also not in effect, the associated footnotes in `unumut_footnotes` apply. If `unumut` is "-unumut" or a variant, there is no un-u-mutation (i.e. there are no special u-mutated stems, and the basic stems, which typically have u-mutation built into them, apply throughout), but the associated footnotes in `unumut_footnotes` still apply in the same circumstances where they would apply if `unumut` were the non-negated counterpart. ** `unumut_footnotes`: See `unumut`. ** `unimut`: If specified (i.e. not nil), either true or false. If specified, there may be associated footnotes in `unimut_footnotes`. If true, i-mutation is in effect *except* in certain case/num combinations that depend on the gender. Specifically: (1) for masculine nouns e.g. [[ketill]] "kettle" and proper names [[Egill]] and [[Ketill]], i-mutation does not apply in the dat sg and throughout the plural; (2) for feminine nouns e.g. [[kýr]] "cow", [[sýr]] "sow (archaic)" and [[ær]] "ewe", i-mutation does not apply in the acc and dat sg and in the dat and gen pl. Cf. also feminine pl-only [[hættur]] "bedtime, quitting time" and [[mætur]] "appreciation, liking", which use 'unimut' to get e.g. dat pl [[háttum]] and gen pl [[hátta]]; but these are handled by synthesizing a singular without i-mutation in the lemma. Very similar are neuter pl [[læti]] "behavior, demeanor" and [[ólæti]] "noise, racket", with e.g. dat pl [[látum]] and gen pl [[láta]], which are handled in the same way. When i-mutation is *not* in effect, the associated footnotes in `unimut_footnotes` apply. If false, the associated footnotes in `unimut_footnotes` still apply in the same circumstances where they would apply if `unimut` where true. ** `unimut_footnotes`: See `unimut`. ]=] local function create_base() return { forms = {}, overrides = {}, props = {}, addnote_specs = {}, } end -- Return true if `stem` refers to a proper noun (first character is uppercase, second character is lowercase). local function is_proper_noun(base, stem) if base.props.common or base.props.dem then return false end if base.props.proper then return true end if base.source_template == "is-noun" then return false end if base.source_template == "is-proper noun" then return true end local first_letter = usub(stem, 1, 1) local second_letter = usub(stem, 2, 2) return ulower(first_letter) ~= first_letter and ((not second_letter or second_letter == "") or uupper(second_letter) ~= second_letter) end --[=[ Basic function to combine stem(s) and other properties with ending(s) and insert the result into the appropriate slot. `base` is the object describing all the properties of the word being inflected for a single alternant (in case there are multiple alternants specified using `((...))`). `slot_prefix` is either "ind_" or "def_" and is prefixed to the slot value in `slot` to get the actual slot to add the resulting forms to. (`slot_prefix` is separated out because the code below frequently needs to conditionalize on the value of `slot` and should not have to worry about the definite and indefinite slot variants). `props` is a property set object containing computed stems and other information (such as whether i-mutation is active) about a particular combination of control specs. See the comment above create_base() for more information. The information found in `props` cannot be stored in `base` because there may be more than one set of such properties per `base` (e.g. if the user specified 'umut,uUmut' or '-j,j' or '-imut,imut' or some combination of these; in such a case, the caller will iterate over all possible combinations, and ultimately invoke add() multiple times, one per combination). `endings` is the ending or endings added to the appropriate stem (after any j or v infix) to get the form(s) to add to the slot. Its value can be a single string, a list of strings, or a list of form objects (i.e. in general list form). `clitics` is the clitic or clitics to add after the endings to form the actual form value inserted into definite slots; it should be nil for indefinite slots. Its format is the same as for `endings`. `ending_override`, if true, indicates that the ending(s) supplied in `endings` come from a user-specified override, and hence j and v infixes should not be added as they are already included in the override if needed. ]=] local function add_slotval(base, slot_prefix, slot, props, endings, clitics, ending_override) if not endings then return end -- Call skip_slot() based on the declined number and definiteness; if the actual number is different, we correct -- this in decline_noun() at the end. if skip_slot(base.number, base.definiteness, slot) then return end if not clitics then clitics = { "" } elseif type(clitics) == "string" then clitics = { clitics } end if type(endings) == "string" then endings = { endings } end -- Loop over each ending and clitic. for _, endingobj in ipairs(endings) do for _, cliticobj in ipairs(clitics) do -- Do the following inside of the innermost loop even though it does not depend on the value of `cliticobj`, -- because that way we are free to mutate `ending` below. local ending, ending_footnotes if type(endingobj) == "string" then ending = endingobj else ending = endingobj.form ending_footnotes = endingobj.footnotes end -- Ending of "-" means the user used -- to indicate there should be no form here. if ending == "-" then return end local function interr(msg) error(("Internal error: For lemma '%s', slot '%s%s', ending '%s', %s: %s"):format(base.lemma, slot_prefix, slot, ending, msg, dump(props))) end local clitic, clitic_footnotes if type(cliticobj) == "string" then clitic = cliticobj else clitic = cliticobj.form clitic_footnotes = cliticobj.footnotes end -- Compute whether i-mutation or u-mutation is in effect, and compute the "mutation footnotes", which are -- footnotes attached to a mutation-related indicator and which may need to be added even if no mutation is -- in effect (specifically when dealing with an ending that would trigger a mutation if in effect). AFAIK -- you cannot have both mutations in effect at once, and i-mutation overrides u-mutation if both would be in -- effect. -- Single ^ at the beginning of an ending indicates that the i-mutated version of the stem should apply, and -- double ^^ at the beginning indicates that the u-mutated version should apply. local explicit_imut, explicit_umut -- % at the end of a definite ending indicates that the following i- of the clitic should drop, as with -- neuter [[tré]], [[kné]], [[fé]]. There's no counterpart to force irregular inclusion of an i- that would -- normally drop; just include it in the ending (as with acc/dat sg of [[eygló]] "eyeball???" and [[sígó]] -- "cig"). local clitic_i_drops ending, explicit_umut = rsubb(ending, "^%^%^", "") if not explicit_umut then ending, explicit_imut = rsubb(ending, "^%^", "") end ending, clitic_i_drops = rsubb(ending, "%%$", "") local is_vowel_ending = rfind(ending, "^" .. com.vowel_c) local is_vowel_clitic = rfind(clitic, "^" .. com.vowel_c) local mut_in_effect, mut_not_in_effect, mut_footnotes local ending_in_a = not not ending:find("^a") local ending_in_i = not not ending:find("^i") local ending_in_u = not not ending:find("^u") if props.unimut ~= nil and props.unumut ~= nil then interr("Cannot have both 'unimut' and 'unumut' in effect at the same time") end if props.unimut ~= nil and props.imut ~= nil then interr("Cannot have both 'unimut' and 'imut' in effect at the same time") end if props.unumut ~= nil and props.umut ~= nil then interr("Cannot have both 'unumut' and 'umut' in effect at the same time") end if explicit_imut then mut_in_effect = "i" elseif explicit_umut then mut_in_effect = "u" else if props.unimut ~= nil then local is_unimut_slot if base.gender == "m" then is_unimut_slot = slot == "dat_s" or slot:find("_p") elseif base.gender == "f" then is_unimut_slot = slot == "acc_s" or slot == "dat_s" or slot == "dat_p" or slot == "gen_p" else interr( "'unimut' shouldn't be specified with neuter nouns; don't know what slots would be affected; neuter pluralia tantum nouns using 'unimut' should have synthesized a singular without i-mutation") end if is_unimut_slot then mut_not_in_effect = "i" mut_footnotes = props.unimut_footnotes elseif props.unimut then mut_in_effect = "i" end elseif props.imut ~= nil then if ending_in_i then if props.imut then mut_in_effect = "i" mut_footnotes = props.imut_footnotes elseif props.imut == false then mut_not_in_effect = "i" mut_footnotes = props.imut_footnotes end end end if props.unumut ~= nil then local is_unumut_slot if props.unumut == "unumut" or props.unumut == "-unumut" then is_unumut_slot = ending_in_a or ending_in_i elseif base.gender == "m" then is_unumut_slot = slot == "gen_s" or slot == "gen_p" elseif base.gender == "f" then is_unumut_slot = slot == "nom_p" or slot == "acc_p" or slot == "gen_p" else interr( "'unumut' and variants shouldn't be specified with neuter nouns; don't know what slots would be affected; neuter pluralia tantum nouns using 'unumut'and variants should have synthesized a singular without u-mutation") end if not mut_in_effect and not mut_not_in_effect then -- Do nothing if mut_in_effect or mut_not_in_effect because i-mut takes precedence over u-mut; -- FIXME: I hope this is correct in all cases. if is_unumut_slot then mut_not_in_effect = "u" mut_footnotes = props.unumut_footnotes elseif props.unumut then mut_in_effect = "u" end end end if ending_in_u and not mut_in_effect then mut_in_effect = "u" -- umut and uUmut footnotes are incorporated into the appropriate umut_* stems end end local ending_was_asterisk = ending == "*" -- Now compute the appropriate stem to which the ending and clitic are added. `prefix` is either an empty -- string or "pl_" and selects the set of stems to consider when computing the stem in effect. See the -- comment above for `pl_stem`. local function compute_stem_in_effect(prefix) local stem_in_effect if mut_in_effect == "i" then -- NOTE: It appears that imut and defcon never co-occur; otherwise we'd need to flesh out the set of -- stems to include i-mutation versions of defcon stems, similar to what we do for u-mutation. if is_vowel_ending then if not props[prefix .. "imut_vstem"] then interr(("i-mutation in effect and ending begins with a vowel but '.%simut_vstem' not defined") : format(prefix)) end stem_in_effect = props[prefix .. "imut_vstem"] else if not props[prefix .. "imut_nonvstem"] then interr(("i-mutation in effect and ending does not begin with a vowel but '.%simut_nonvstem' not defined") : format(prefix)) end stem_in_effect = props[prefix .. "imut_nonvstem"] end else -- Careful with the following logic; it is written carefully and should not be changed without a -- thorough understanding of its functioning. local has_umut = mut_in_effect == "u" -- First, if the ending is null (or "*", which eventually turns into a null ending; see below), and -- we have a vowel-initial definite-article clitic, use the special 'defcon' stem if available. if (ending == "" or ending == "*") and is_vowel_clitic then stem_in_effect = has_umut and props[prefix .. "umut_null_defvstem"] or props[prefix .. "null_defvstem"] end -- If the stem is still unset, then use the vowel or non-vowel stem if available. When u-mutation is -- active, we first check for the u-mutated version of the vowel or non-vowel stem before falling -- back to the regular vowel or non-vowel stem. Note that an expression like `has_umut and -- props[prefix .. "umut_vstem"] or props[prefix .. "vstem"]` here is NOT equivalent to an if-else -- or ternary operator expression because if `has_umut` is true and `umut_vstem` is missing, it will -- still fall back to `vstem` (which is what we want). if not stem_in_effect then if is_vowel_ending then stem_in_effect = has_umut and props[prefix .. "umut_vstem"] or props[prefix .. "vstem"] else stem_in_effect = has_umut and props[prefix .. "umut_nonvstem"] or props[prefix .. "nonvstem"] end end -- Finally, fall back to the basic stem, which is always defined. stem_in_effect = stem_in_effect or props[prefix .. "stem"] end -- If the ending is "*", it means to use the lemma as the form directly (before adding any definite -- clitic) rather than try to construct the form from a stem and ending. We need to do this for the -- lemma slot and especially for the nominative singular, because we don't have the nominative singular -- ending available and it may vary (e.g. it may be -ur, -l, -n, -a, etc. especially in the masculine). -- Not trying to construct the form from stem + ending also avoids complications from the nominative -- singular in -ur, which exceptionally does not trigger u-mutation. However, when 'defcon' is active -- and we're processing a definite form beginning with a vowel (i.e. is_vowel_clitic is set), we can't -- do this, because the form to which the clitic is added is not the lemma but the contracted version. -- As it happens, this works out because in all situations where 'defcon' is active, the nominative -- singular has a null ending. (If this weren't the case, we'd have to change all the declension -- functions to pass in the nominative singular ending in addition to other endings.) An example where -- 'defcon' is active is neuter [[mastur]] "mast" with definite nominative singular [[mastrið]]; here, -- using the lemma would incorrectly produce #[[masturið]]. -- Finally, however, if there is a footnote associated with the computed stem in effect, we need to -- preserve it. if ending == "*" then if not is_vowel_clitic or not props.defcon or props.defcon.form ~= "defcon" then local stem_in_effect_footnotes if type(stem_in_effect) == "table" then stem_in_effect_footnotes = stem_in_effect.footnotes end stem_in_effect = iut.combine_form_and_footnotes(base.actual_lemma, stem_in_effect_footnotes) end -- See comment above. When 'defcon' is not in effect, we changed the stem to be the lemma and -- want to use a null ending; otherwise, the ending is always null anyway, so it's safe to set -- it thus. ending = "" end return stem_in_effect end local stem_in_effect = props.pl_stem and slot:find("_p$") and compute_stem_in_effect("pl_") or compute_stem_in_effect("") local infix, infix_footnotes -- Compute the infix (j, v or nothing) that goes between the stem and ending. if not ending_override and is_vowel_ending then if props.vinfix and props.jinfix then interr("Can't have specifications for both '.vinfix' and '.jinfix'; should have been caught above") end if props.vinfix then infix = props.vinfix infix_footnotes = props.vinfix_footnotes elseif props.jinfix and not ending_in_i then infix = props.jinfix infix_footnotes = props.jinfix_footnotes end end -- If base-level footnotes specified, they go before any stem footnotes, so we need to extract any footnotes -- from the stem in effect and insert the base-level footnotes before. In general, we want the footnotes to -- be in the order [base.footnotes, stem.footnotes, mut_footnotes, infix_footnotes, ending.footnotes, -- clitic.footnotes]. if base.footnotes then local stem_in_effect_footnotes if type(stem_in_effect) == "table" then stem_in_effect_footnotes = stem_in_effect.footnotes stem_in_effect = stem_in_effect.form end stem_in_effect = iut.combine_form_and_footnotes(stem_in_effect, iut.combine_footnotes(base.footnotes, stem_in_effect_footnotes)) end local ending_is_full ending, ending_is_full = rsubb(ending, "^!", "") local function combine_stem_ending(stem, clitic) if stem == "?" then return "?" end local function drop_clitic_i() clitic = clitic:gsub("^i", "") end -- If we're definite-only and using the actual lemma as the stem, the clitic is already incorporated -- into the stem. if base.definiteness == "def" and ending_was_asterisk then return stem end -- % at the end of a definite ending indicates that the following i- of the clitic should drop; see -- above. if clitic_i_drops then drop_clitic_i() end local stem_with_infix = ending_is_full and "" or stem .. (infix or "") -- Drop final -j- of stem before an ending beginning with a consonant. This happens e.g. in [[kirkja]] -- "church" with genitive plural -na, producing [[kirkna]]. It does not happen with a null ending; cf. -- neuter [[emj]] "cries, shouting" and [[gremj]] "anger, irritation" (the latter not in BÍN). if stem_with_infix:find("j$") and rfind(ending, "^" .. com.cons_c) then stem_with_infix = stem_with_infix:gsub("j$", "") end local stem_with_ending -- An initial s- of the ending drops after a cluster of cons + s (including written <x>). if ending:find("^s") and (stem_with_infix:find("x$") or rfind(stem_with_infix, com.cons_c .. "s$")) then stem_with_ending = stem_with_infix .. ending:gsub("^s", "") else stem_with_ending = stem_with_infix .. ending end if clitic == "" then return stem_with_ending end if slot == "dat_p" then stem_with_ending = stem_with_ending:gsub("m$", "") end if clitic:find("^i.*[aiu]") then -- disyllabic clitics in i- -- in practice, fem acc_s -ina, dat_s -inni, gen_s -innar if rfind(stem_with_ending, com.vowel_c .. "$") then drop_clitic_i() end elseif clitic:find("^i") then -- monosyllabic clitics in i- local ending_for_clitic_dropping = ending_was_asterisk and base.lemma_ending or ending if ending_for_clitic_dropping:find("[aiu]$") then drop_clitic_i() end end return stem_with_ending .. clitic end local combined_footnotes = iut.combine_footnotes( iut.combine_footnotes(mut_footnotes, infix_footnotes), iut.combine_footnotes(ending_footnotes, clitic_footnotes) ) local clitic_with_notes = iut.combine_form_and_footnotes(clitic, combined_footnotes) if not stem_in_effect then interr("stem_in_effect is nil") end iut.add_forms(base.forms, slot_prefix .. slot, stem_in_effect, clitic_with_notes, combine_stem_ending) end end end -- Add the definite and indefinite variants of a slot by combining the appropriate stem in `props` with (optionally) an -- infix in `props` and the endings in `endings`, tacking on the definite article clitic in the definite slot variant. -- This calls the underlying function add_slotval() twice, once for indefinite forms and once for definite forms, and is -- normally called by add_decl() or similar function to add an entire declension. `endings` can be nil (no endings are -- added), a single string, a list of strings, a list of form objects (i.e. in general list form), or a table containing -- fields `indef` and `def` (each of which can be any of the previous formats) to add separate sets of endings for the -- indefinite and definite slot variants. If any of the formats for `endings` is supplied other than the separate -- indefinite/definite table, the supplied set of endings is used for both indefinite and definite slot variants. -- `ending_override` and `endings_are_full` are as in add_slotval(). local function add(base, slot, props, endings, ending_override, endings_are_full) if not endings then return end local indef_endings, def_endings if type(endings) == "table" and (endings.indef or endings.def) then indef_endings = endings.indef def_endings = endings.def else indef_endings = endings def_endings = endings end if indef_endings and base.definiteness ~= "def" then add_slotval(base, "ind_", slot, props, indef_endings, nil, ending_override, endings_are_full) end if def_endings and (base.definiteness ~= "indef" and base.definiteness ~= "none") then local clitic = clitic_articles[base.gender] if not clitic then error(("Internal error: Unrecognized value for base.gender: %s"):format(dump(base.gender))) end clitic = clitic[slot] if not clitic then error(("Internal error: Unrecognized value for `slot` in add(): %s"):format(dump(slot))) end add_slotval(base, "def_", slot, props, def_endings, clitic, ending_override, endings_are_full) end end -- Generate the accusative plural ending from the nominative plural. For feminines and neuters, both are the same. -- For masculines, drop the -r except in -ur. local function acc_p_from_nom_p(base, nom_p) if base.gender == "f" or base.gender == "n" then return nom_p end if not nom_p then return nom_p -- this is correct as `nom_p` could be nil or false and we want to return the same thing end local function form_masc_acc_p(ending) -- Form the masculine accusative by dropping -r unless the form ends in -ur, which is kept. If the ending is *, -- we substitute the entire actual lemma. In that case, if the lemma is definite-only, we have to strip off -- the nominative plural clitic -nir before generating the accusative. We don't add the clitic -na because it -- will be added in add_slotval(). if ending == "*" then ending = "!" .. base.actual_lemma end if base.definiteness == "def" and ending:find("^!") then ending = ending:match("^(.*)nir$") if not ending then error(("Masculine plural definite-only lemma '%s' does not end in expected clitic '-nir'; " .. "don't know how to compute the corresponding accusative plural"):format(base.actual_lemma)) end end -- If the ending is full (begins with !), check the whole thing for -ur at the end. if ending:find("^%^*ur$") or ending:find("^!.*[^Aa]ur$") then -- as-is else ending = ending:gsub("r$", "") end return ending end if type(nom_p) == "string" then return form_masc_acc_p(nom_p) end local acc_p = {} for _, ending in ipairs(nom_p) do if type(ending) == "string" then table.insert(acc_p, form_masc_acc_p(ending)) else table.insert(acc_p, { form = form_masc_acc_p(ending.form), footnotes = ending.footnotes }) end end return acc_p end local function process_one_slot_override(base, slot, spec) -- Call skip_slot() based on the declined number and definiteness; if the actual number is different, we correct -- this in decline_noun() at the end. if skip_slot(base.number, base.definiteness, slot) then error(("Override specified for invalid slot '%s' due to '%s' number restriction and/or '%s' definiteness restriction") :format( slot, base.number, base.definiteness)) end local defslot = slot:find("^def_") if defslot then base.forms[slot] = nil else if spec.indef ~= false then base.forms["ind_" .. slot] = nil end if spec.def ~= false then base.forms["def_" .. slot] = nil end end if defslot then local slot_prefix -- Don't call add(), like below, because it adds both indefinite and definite variants, including definite -- clitics in the latter. Instead, directly call add_slotval(). But we need to separate the slot into slot -- prefix "def_" and the remainder because add_slotval() expects slots to be missing the prefix when -- checking which stem to use (which may depend on the slot). slot_prefix, slot = slot:match("^(def_)(.*)$") for _, props in ipairs(base.prop_sets) do add_slotval(base, slot_prefix, slot, props, spec.def, nil, "ending override") end else local endings if spec.indef ~= nil and spec.def ~= nil then -- This could include `false` as the value of either `spec.indef` or `spec.def` to not touch those slots. -- Note that specifying something like 'dat/i' is allowed and will only override the definite slot, but -- is different from a definite-slot override 'defdatinum' because the latter includes the clitic in it. endings = { indef = spec.indef, def = spec.def, } elseif not spec.indef then error(("Internal error: Unless both `spec.indef` and `spec.def` have non-nil values (i.e. the user included a slash in the override, `spec.indef` must be defined: %s") :dump(spec)) elseif slot == "acc_p" then -- As a special case, don't carry over literary acc_p ending -u to the definite. local def_endings = {} for _, ending in ipairs(spec.indef) do -- If the ending is full (begins with !), check the whole thing for -u at the end. if not ending.form:find("^%^*u$") and not ending.form:find("^!.*[^Aa]u$") then table.insert(def_endings, ending) end end endings = { indef = spec.indef, def = def_endings, } else endings = spec.indef end for _, props in ipairs(base.prop_sets) do add(base, slot, props, endings, "ending override") end end end local function process_slot_overrides(base) if base.gens then process_one_slot_override(base, "gen_s", base.gens) end if base.pls then local spec = base.pls process_one_slot_override(base, "nom_p", spec) local acc_p_spec = { indef = acc_p_from_nom_p(base, spec.indef), def = acc_p_from_nom_p(base, spec.def), } process_one_slot_override(base, "acc_p", acc_p_spec) end for slot, spec in pairs(base.overrides) do process_one_slot_override(base, slot, spec) end end -- Generate the full declension for the term given the endings for each slot. acc_p, dat_p and gen_p can be omitted and -- will be defaulted: dat_p defaults to "um", gen_p defaults to "a", and acc_p defaults to the nom_p except for masculines -- not in -ur, where the -r is dropped. Use `false` as the value of an ending to disable generating any value for that -- slot. local function add_decl_with_nom_sg(base, props, nom_s, acc_s, dat_s, gen_s, nom_p, acc_p, dat_p, gen_p) add(base, "nom_s", props, nom_s) add(base, "acc_s", props, acc_s) add(base, "dat_s", props, dat_s) add(base, "gen_s", props, gen_s) if base.number == "pl" then -- If this is a plurale tantum noun and we're processing the nominative plural, use the user-specified lemma -- rather than generating the plural from the synthesized singular, which may not match the specified lemma. -- This is both because we don't set a plural override to specify what the plural should look like and because -- of exceptional cases like [[dyr]], which is plural-only and uses 'decllemma:dyrir'. nom_p = "*" end add(base, "nom_p", props, nom_p) -- Generate defaults for acc_p, dat_p, gen_p if nil was specified; but be careful not to do so for false, which -- means to generate no form. if acc_p == nil then acc_p = acc_p_from_nom_p(base, nom_p) end if dat_p == nil then dat_p = "um" end if gen_p == nil then gen_p = "a" end add(base, "acc_p", props, acc_p) add(base, "dat_p", props, dat_p) add(base, "gen_p", props, gen_p) end -- Generate the full declension for the term given the endings for each slot except the nom_s. This is like -- add_decl_with_nom_sg() but takes the nom sg directly from the lemma instead of trying to reconstruct it from a stem, -- which is more correct in the vast majority of circumstances. The * below is a signal to the underlying add() function -- to use the actual lemma (not any stem, and not the value of 'decllemma:' if given) for the nom sg. Note that add() is -- smart enough to ignore this for the definite nom sg when the 'defcon' indicator is given, because in that case the stem -- for the def nom sg is contracted compared with the lemma. (Specifically, it uses the correct contracted stem and a null -- ending; AFAIK all cases of 'defcon' occur with lemmas with a null ending in the nom sg.) local function add_decl(base, props, acc_s, dat_s, gen_s, nom_p, acc_p, dat_p, gen_p) add_decl_with_nom_sg(base, props, "*", acc_s, dat_s, gen_s, nom_p, acc_p, dat_p, gen_p) end local function add_sg_decl(base, props, acc_s, dat_s, gen_s) add_decl(base, props, acc_s, dat_s, gen_s, false, false, false, false) end local function add_pl_only_decl(base, props, acc_p, dat_p, gen_p) add_decl(base, props, false, false, false, "*", acc_p, dat_p, gen_p) end -- Table mapping declension types to functions to decline the noun. The function takes two arguments, `base` and -- `props`; the latter specifies the computed stems (vowel vs. non-vowel, singular vs. plural) and whether the noun -- is reducible and/or has vowel alternations in the stem. Most of the specifics of determining which stem to use -- and how to modify it for the given ending are handled in add_decl(); the declension functions just need to generate -- the appropriate endings. local decls = {} decls["indecl"] = function(base, props) add_decl(base, props, "", "", "", "", "", "", "") end decls["decl?"] = function(base, props) add_decl(base, props, "?", "?", "?", "?", "?", "?", "?") end decls["m"] = function(base, props) -- The default dative singular is computed below in determine_default_masc_dat_sg(). local dat = props.default_dat_sg add_decl(base, props, "", dat, "s", "ar") end decls["m-ir"] = function(base, props) add_decl(base, props, "i", "i", "is", "ar") end decls["m-skapur"] = function(base, props) -- Nouns in -skapur; default gen is -ar, default dat is -/-, default num is sg. add_decl(base, props, "", "", "ar", "ar") end decls["m-naður"] = function(base, props) -- Nouns in -naður; default gen is -ar, default dat is dati/i:-, default nom pl is -ir, default num is sg, -- default u-mutation is uUmut. add_decl(base, props, "", { indef = "i", def = { "i", "" } }, "ar", "ir") end decls["m-kell"] = function(base, props) -- Proper nouns in -kell; [[Þorkell]], [[Grímkell]], etc. local alt_dat_s = base.stem:gsub("kel$", "katli") add_decl(base, props, "", { "i", { form = "!" .. alt_dat_s, footnotes = { "[archaic]" } } }, "s", false, false, false, false) end decls["m-ó"] = function(base, props) -- abbreviations of school names generally have null genitive: [[Kennó]] from [[Kennaraskóla]] "Teachers' College"), -- [[Astró]], [[Borgó]] (from [[Borgarholtsskóli]]), [[Bríó]], [[Foldó]] (from [[Foldaskóli]]), [[Hafró]] (from -- [[Hafrannsóknastofnun]] "Marine Research Institute" (of Norway), [[Hagó]] (from [[Hagaskóli]]), [[Húsó]], -- [[Kvennó]] (from [[Kvennaskóli]]), [[Meló]] (from [[Melaskóli]]), [[Menntó]] (from [[Menntaskóli]]), [[Tónó]] -- (from [[Tónlistarskóli]]), [[Való]] (from [[Valhúsaskóli]]), [[Versló]]/[[Verzló]] (from -- [[Verslunarskóli Íslands|Iceland Business School]]); but these are completely outweighed by male given names, -- nicknames and historical names of men in -ó (e.g. [[Bó]], [[Bóbó]], [[Brúnó]], [[Dittó]], [[Filpó]], [[Galíleó]], -- [[Jagó]], [[Kató]], [[Kristó]], [[Leó]], [[Leónardó]], [[Markó]], etc.) as well as common nouns in -ó (e.g. -- [[bóleró]] "bolero", [[evró]] "Euro (dated)", [[faraó]] "pharaoh", [[kanó]] "canoe", [[kímonó]] "kimono", -- [[mambó]] "mambo", [[pesó]] "peso", [[pikkóló]] "piccolo", [[róló]] "playground", [[sleikjó]] "lollipop", -- etc.) add_decl(base, props, "", "", "s", "ar") end decls["m-rstem"] = function(base, props) local imut = "^" add_decl(base, props, "ur", "ur", { "ur", { form = "urs", footnotes = { "[proscribed]" } } }, imut .. "ur", nil, imut .. "rum", imut .. "ra") end decls["m-ndi"] = function(base, props) -- Words in -ndi, mostly derived from present participles and mostly in [[andi]]; but cf. [[bóndi]], [[frændi]], -- and [[fjandi]] with two plurals with different meanings. local imut if props.stem:find("ænd$") then imut = "" else imut = "^" end add_decl(base, props, "a", "a", "a", imut .. "ur", nil, { imut .. "um", { form = "um", footnotes = "[rare/obsolete]" } }, { imut .. "a", { form = "a", footnotes = "[rare/obsolete]" } }) end decls["m-weak"] = function(base, props) -- Words in -i like [[tími]] "time, hour"; also words in -a e.g. [[herra]] "gentleman; sir, Mr. (term of address)", -- [[séra]]/[[síra]] "reverend" add_decl(base, props, "a", "a", "a", "ar") end decls["f"] = function(base, props) -- Normal strong feminine nouns; default to genitive -ar, plural -ir. add_decl(base, props, "", "", "ar", "ir") end decls["f-ung"] = function(base, props) -- Strong feminine nouns in -ung, e.g. [[nýjung]] "newness, novelty; piece of news", [[nauðung]] -- "constraint, compulsion". Most such nouns are singular-only, e.g. [[djörfung]] "boldness, daring", [[launung]] -- "secrecy". Occasional nouns need overrides, e.g. [[sundrung]] "scattering; dissension, division, disunity" with -- acc/dat sg either - or -u (but only - in the definite acc/dat sg). add_decl(base, props, "", "", "ar", "ar") end decls["f-ing"] = function(base, props) -- Strong feminine nouns in -ing, e.g. [[kerling]] "old woman", [[eining]] "unity; unit". Singular-only: e.g. -- [[málning] "paint", [[menning]] "culture", [[örvænting]] "despair". add_decl(base, props, "u", "u", "ar", "ar") end decls["f-ur"] = function(base, props) add_decl(base, props, "i", "i", "ar", "ir") end decls["f-i"] = function(base, props) add_decl(base, props, "i", "i", "i", "ir") end decls["f-long-vowel"] = function(base, props) -- nouns in -á, e.g. [[á]] "river", [[gjá]] "gorge, canyon", [[skuggská]] "mirror", [[slá]] "door bolt"; -- nouns in -ó, e.g. [[fló]] "flea", [[kónguló]] "spider", [[kló]] "claw"; -- nouns in -ú, e.g. [[frú]] "married woman", [[trú]] "faith, belief". -- Each is slightly different. local gen, nompl if props.stem:find("á$") then gen = "r" nompl = "r" elseif props.stem:find("ó$") then gen = "ar" nompl = "^r" elseif props.stem:find("ú$") then gen = "ar" nompl = "r" else error(("Unrecognized stem '%s' for long-vowel feminine; should end in -á, -ó or -ú")) end add_decl(base, props, "", "", gen, nompl, nompl, "m", { indef = "a", def = "" }) end decls["f-long-umlaut-vowel-r"] = function(base, props) -- nouns in long umlauted vowel + -r: [[kýr]] "cow", [[sýr]] "sow (archaic)", [[ær]] and compounds. add_decl(base, props, "", "", "^r", "^r", "^r", "m", { indef = "a", def = "" }) end decls["f-acc-dat-i"] = function(base, props) -- Some proper female names with -i in the acc and dat sg add_decl(base, props, "i", "i", "ar", "ar") end decls["f-rstem"] = function(base, props) local imut if props.stem:find("syst$") then imut = "" else imut = "^" end local sg_ending = { "ur", { form = "ir", footnotes = { "[proscribed]" } } } add_decl(base, props, sg_ending, sg_ending, sg_ending, imut .. "ur", nil, imut .. "rum", imut .. "ra") end decls["f-weak"] = function(base, props) add_decl(base, props, "u", "u", "u", "ur") end decls["n"] = function(base, props) -- Normal (strong) neuter nouns. add_decl(base, props, "", "i", "s", "^^") end decls["n-já"] = function(base, props) -- [[tré]] "tree; wood"; [[hné]]/[[kné]] "knee"; [[fé]] "sheep; cattle; money"; the stem has previously been set -- to not include final -é; fé has genitive fjár while the others have genitive in -és. local gen = props.stem:find("f$") and "jár" or "és" add_decl_with_nom_sg(base, props, "é%", "é%", "é", gen, "é%", "é%", "jám", { indef = "jáa", def = "já" }) end decls["n-i"] = function(base, props) -- Neuter nouns in -i, e.g. [[kvæði]] "poem, song". Nouns in -ki and -gi e.g. [[ríki]] "state, kingdom" and [[engi]] -- "meadow" have j-insertion by default, which is set elsewhere. add_decl(base, props, "i", "i", "is", "i") end decls["n-weak"] = function(base, props) -- "Weak" neuter nouns in -a, e.g. [[auga]] "eye", [[hjarta]] "heart". U-mutation occurs in the nom/acc/dat pl but -- doesn't need to be indicated explicitly because the ending begins with u-. add_decl(base, props, "a", "a", "a", "u") end local function reconstruct_control_spec(control_specs) local parts = {} local function ins(txt) table.insert(parts, txt) end for i, spec in ipairs(control_specs) do if i > 1 then ins(",") end ins(spec.form) if spec.footnotes then for _, footnote in ipairs(spec.footnotes) do ins(footnote) -- already has brackets around it end end end return table.concat(parts) end decls["adj"] = function(base, _props) -- This maps from a slot name constructed from the individual state, case, gender and number properties to the -- actual syncretic slot name used in [[Module:is-adjective]]. local slot_to_syncretic_slot_mapping = { str_nom_m_s = "str_nom_m", str_nom_f_s = "str_nom_f", str_nom_n_s = "str_nom_n", str_acc_m_s = "str_acc_m", str_acc_f_s = "str_acc_f", str_acc_n_s = "str_nom_n", str_dat_m_s = "str_dat_m", str_dat_f_s = "str_dat_f", str_dat_n_s = "str_dat_n", str_gen_m_s = "str_gen_m", str_gen_f_s = "str_gen_f", str_gen_n_s = "str_gen_n", str_nom_m_p = "str_nom_mp", str_nom_f_p = "str_nom_fp", str_nom_n_p = "str_nom_np", str_acc_m_p = "str_acc_mp", str_acc_f_p = "str_nom_fp", str_acc_n_p = "str_nom_np", str_gen_m_p = "str_gen_p", str_gen_f_p = "str_gen_p", str_gen_n_p = "str_gen_p", str_dat_m_p = "str_dat_p", str_dat_f_p = "str_dat_p", str_dat_n_p = "str_dat_p", wk_nom_m_s = "wk_nom_m", wk_nom_f_s = "wk_nom_f", wk_nom_n_s = "wk_n", wk_acc_m_s = "wk_obl_m", wk_acc_f_s = "wk_obl_f", wk_acc_n_s = "wk_n", wk_dat_m_s = "wk_obl_m", wk_dat_f_s = "wk_obl_f", wk_dat_n_s = "wk_n", wk_gen_m_s = "wk_obl_m", wk_gen_f_s = "wk_obl_f", wk_gen_n_s = "wk_n", wk_nom_m_p = "wk_p", wk_nom_f_p = "wk_p", wk_nom_n_p = "wk_p", wk_acc_m_p = "wk_p", wk_acc_f_p = "wk_p", wk_acc_n_p = "wk_p", wk_gen_m_p = "wk_p", wk_gen_f_p = "wk_p", wk_gen_n_p = "wk_p", wk_dat_m_p = "wk_p", wk_dat_f_p = "wk_p", wk_dat_n_p = "wk_p", } local props = {} local function ins(prop) table.insert(props, prop) end for _, spectype in ipairs(m_is_adjective.control_specs) do if base[spectype] then ins(reconstruct_control_spec(base[spectype])) end end -- If a specific reverse u-mutation type was specified and no u-mutation was given, convert the reverse -- u-mutation into a regular u-mutation by chopping off the "un" at the beginning. if base.adj_unumut and not base.umut then ins(base.adj_unumut:sub(3)) end for k, _ in pairs(base.props) do if m_is_adjective.boolean_property_set[k] then ins(k) end end if not base.props.builtin then ins(base.props.iscomp and "-pos" or "-comp") end if base.stem == "#" or base.stem == "##" then ins(base.stem) elseif base.stem then ins("stem:" .. base.stem) end for _, stem in ipairs(m_is_adjective.overridable_stems) do if stem ~= "stem" and base[stem] then ins(("%s:%s"):format(stem, base.stem)) end end local propspec = table.concat(props, ".") if propspec ~= "" then propspec = "<" .. propspec .. ">" end local argspec = base.lemma .. propspec local adj_alternant_multiword_spec = m_is_adjective.do_generate_forms({ argspec }, argspec, "is-ndecl") local function copy(from_slot, to_slot, do_clone) -- We want to avoid sharing form objects (although sharing footnotes is OK, but we don't avoid cloning them -- here) so we can later side-effect form objects as needed. `do_clone` is set to avoid such sharing, -- specifically when the weak form of the adjective is used for both definite and indefinite slots. local source = adj_alternant_multiword_spec.forms[from_slot] if do_clone then source = m_table.deepCopy(source) end base.forms[to_slot] = source end local function copy_gender_number_forms(gender, number) local state = base.adj_is_weak and "wk" or "str" local degree_pref = base.props.iscomp and "comp_" or "" for _, case in ipairs(cases) do local individual_slot = state .. "_" .. case .. "_" .. gender .. "_" .. number local wk_individual_slot = "wk_" .. case .. "_" .. gender .. "_" .. number local syncretic_slot = slot_to_syncretic_slot_mapping[individual_slot] local wk_syncretic_slot = slot_to_syncretic_slot_mapping[wk_individual_slot] if not syncretic_slot then error(("Internal error: Constructed bad individual slot '%s' with no entry in syncretic slot mapping"): format(individual_slot)) end syncretic_slot = degree_pref .. syncretic_slot if not wk_syncretic_slot then error(("Internal error: Constructed bad weak individual slot '%s' with no entry in syncretic slot mapping") : format(wk_individual_slot)) end wk_syncretic_slot = degree_pref .. wk_syncretic_slot copy(syncretic_slot, "ind_" .. case .. "_" .. number) copy(wk_syncretic_slot, "def_" .. case .. "_" .. number, syncretic_slot == wk_syncretic_slot) end end if base.number ~= "pl" then copy_gender_number_forms(base.gender, "s") end if base.number ~= "sg" then copy_gender_number_forms(base.gender, "p") end end local function set_builtin_defaults(base) if base.gender or base.number or base.definiteness then error("Can't specify gender, number or definiteness for built-in terms") end local function builtin_props() -- Return values are GENDER, NUMBER if base.lemma == "ég" or base.lemma == "þú" then return "none", "sg" elseif base.lemma == "við" or base.lemma == "þið" then return "none", "pl" elseif base.lemma == "hann" then return "m", "sg" elseif base.lemma == "hún" then return "f", "sg" elseif base.lemma == "það" then return "n", "sg" elseif base.lemma == "þeir" then return "m", "pl" elseif base.lemma == "þær" then return "f", "pl" elseif base.lemma == "þau" then return "n", "pl" elseif base.lemma == "sig" then return "none", "none" else error(("Unrecognized pronoun '%s'"):format(base.lemma)) end end local gender, number = builtin_props() base.gender = gender base.actual_gender = gender base.number = number base.actual_number = number base.definiteness = "none" end local function determine_builtin_props(base) base.prop_sets[1].stem = { form = "" } base.decl = "builtin" end decls["builtin"] = function(base, props) if base.lemma == "ég" then add_sg_decl(base, props, "mig", "mér", "mín") elseif base.lemma == "þú" then add_sg_decl(base, props, "þig", "þér", "þín") elseif base.lemma == "hann" then add_sg_decl(base, props, "hann", "honum", "hans") elseif base.lemma == "hún" then add_sg_decl(base, props, "hana", "henni", "hennar") elseif base.lemma == "það" then add_sg_decl(base, props, "það", "því", "þess") elseif base.lemma == "við" then add_pl_only_decl(base, props, "okkur", "okkur", "okkar") elseif base.lemma == "þið" then add_pl_only_decl(base, props, "ykkur", "ykkur", "ykkar") elseif base.lemma == "þeir" then add_pl_only_decl(base, props, "þá", "þeim", "þeirra") elseif base.lemma == "þær" then add_pl_only_decl(base, props, "þær", "þeim", "þeirra") elseif base.lemma == "þau" then add_pl_only_decl(base, props, "þau", "þeim", "þeirra") elseif base.lemma == "sig" then -- Underlyingly we handle [[sig]]'s slots as singular. add_decl_with_nom_sg(base, props, false, "*", "sér", "sín", false, false, false, false) else error(("Internal error: Unrecognized pronoun lemma '%s'"):format(base.lemma)) end end -- Return the lemmas for this term. The return value is a list of {form = FORM, footnotes = FOOTNOTES}. -- If `linked_variant` is given, return the linked variants (with embedded links if specified that way by the user), -- otherwies return variants with any embedded links removed. If `remove_footnotes` is given, remove any -- footnotes attached to the lemmas. function export.get_lemmas(alternant_multiword_spec, linked_variant, remove_footnotes) local slots_to_fetch = potential_lemma_slots local linked_suf = linked_variant and "_linked" or "" for _, slot in ipairs(slots_to_fetch) do if alternant_multiword_spec.forms[slot .. linked_suf] then local lemmas = alternant_multiword_spec.forms[slot .. linked_suf] if remove_footnotes then local lemmas_no_footnotes = {} for _, lemma in ipairs(lemmas) do table.insert(lemmas_no_footnotes, { form = lemma.form }) end return lemmas_no_footnotes else return lemmas end end end return {} end local function handle_derived_slots_and_overrides(base) -- Process slot overrides: First slots specified after the gender, then individual slot overrides specified as -- separate indicators. process_slot_overrides(base) -- Compute linked versions of potential lemma slots, for use in {{is-noun}}. We substitute the original lemma -- (before removing links) for forms that are the same as the lemma, if the original lemma has links. for _, slot in ipairs(potential_lemma_slots) do iut.insert_forms(base.forms, slot .. "_linked", iut.map_forms(base.forms[slot], function(form) if form == base.orig_lemma_no_links then if base.orig_lemma:find("%[%[") then return base.orig_lemma elseif not base.is_multiword then return form elseif not base.props.linkasis and (base.lemma ~= base.orig_lemma_no_links or base.link_lowercase) then local lemma_for_linking = base.lemma if base.link_lowercase then local init, rest = rmatch(lemma_for_linking, "^(.)(.*)$") lemma_for_linking = ulower(init) .. rest end return ("[[%s|%s]]"):format(lemma_for_linking, base.orig_lemma_no_links) else return ("[[%s]]"):format(form) end else return form end end)) end end -- Process specs given by the user using 'addnote[SLOTSPEC][FOOTNOTE][FOOTNOTE][...]'. local function process_addnote_specs(base) for _, spec in ipairs(base.addnote_specs) do for _, slot_spec in ipairs(spec.slot_specs) do slot_spec = "^" .. slot_spec .. "$" for slot, forms in pairs(base.forms) do if rfind(slot, slot_spec) then -- To save on memory, side-effect the existing forms. for _, form in ipairs(forms) do form.footnotes = iut.combine_footnotes(form.footnotes, spec.footnotes) end end end end end end local function is_regular_noun(base) return not base.adjspec and not base.props.builtin end local function process_declnumber(base) base.actual_number = base.number if base.declnumber then if base.declnumber == "sg" or base.declnumber == "pl" then base.number = base.declnumber else error(("Unrecognized value '%s' for 'declnumber', should be 'sg' or 'pl'"):format(base.declnumber)) end end end -- Map `fn` over an override spec (either `gens`, `pls` or one of the overrides in `overrides`). `fn` is passed one -- item (the form object of the override), which it can mutate if needed. If it ever returns non-nil, mapping stops -- and that value is returned as the return value of `map_override`; otherwise mapping runs to completion and nil is -- returned. local function map_override(override, fn) if not override then return nil end local function map_one_list(list) if not list then return nil end for _, formobj in ipairs(list) do local retval = fn(formobj) if retval ~= nil then return retval end end return nil end local retval = map_one_list(override.indef) if retval ~= nil then return retval end return map_one_list(override.def) end -- Map `fn` over all override specs in `base` (`gens`, `pls` and the overrides in `overrides`). `fn` is passed one -- item (the form object of the override), which it can mutate if needed. If it ever returns non-nil, mapping stops -- and that value is returned as the return value of `map_override`; otherwise mapping runs to completion and nil is -- returned. local function map_all_overrides(base, fn) for slot, override in pairs(base.overrides) do local retval = map_override(override, fn) if retval ~= nil then return retval end end local retval = map_override(base.gens, fn) if retval ~= nil then return retval end return map_override(base.pls, fn) end -- Like put.split_alternating_runs_and_strip_spaces(), but ensure that backslash-escaped commas and periods are not -- treated as separators. local function split_alternating_runs_with_escapes(segments, splitchar) for i, segment in ipairs(segments) do segment = rsub(segment, "\\,", SUB_ESCAPED_COMMA) segments[i] = rsub(segment, "\\%.", SUB_ESCAPED_PERIOD) end local separated_groups = put.split_alternating_runs_and_strip_spaces(segments, splitchar) for _, separated_group in ipairs(separated_groups) do for i, segment in ipairs(separated_group) do segment = rsub(segment, SUB_ESCAPED_COMMA, ",") separated_group[i] = rsub(segment, SUB_ESCAPED_PERIOD, ".") end end return separated_groups end local function fetch_footnotes(separated_group, parse_err) local footnotes for j = 2, #separated_group - 1, 2 do if separated_group[j + 1] ~= "" then parse_err("Extraneous text after bracketed footnotes: '" .. table.concat(separated_group) .. "'") end if not footnotes then footnotes = {} end table.insert(footnotes, separated_group[j]) end return footnotes end -- Fetch and parse a slot override, e.g. "ar:s" or "um:m[archaic]/um" or "i:!Þorkatli[archaic]" (where ! indicates that -- the override is the full form including the stem); that is, everything after the slot name(s). `segments` is the -- input in the form of a list where the footnotes have been separated out (see `parse_override` below); `spectype` is -- used in error messages and specifies e.g. "genitive" or "dat+gen slot override"; `allow_blank` indicates that a -- completely blank override spec is allowed (in that case, nil will be returned); `defslot`, if true, indicates that -- we're processing a definite slot override, i.e. two slash-separated specs (indefinite and definite) are not allowed -- and the return overrides will be stored into `def`; and `parse_err` is a function of one argument to throw a parse -- error. The return value is an object containing fields `indef` and/or `def`, of the format described below in the -- comment above `parse_override`. local function fetch_slot_override(segments, spectype, allow_blank, defslot, parse_err) if allow_blank and #segments == 1 and segments[1] == "" then return nil end local slash_separated_groups = put.split_alternating_runs_and_strip_spaces(segments, "/") if #slash_separated_groups > 2 then parse_err(("Can specify at most two slash-separated override groups for %s, but saw %s"):format( spectype, #slash_separated_groups)) end if slash_separated_groups[2] and defslot then parse_err(("Can't specify two slash-separated override groups for %s; the second override group is for the definite slot variant, but the slot is already definite") :format( spectype)) end local ret = {} for i, slash_separated_group in ipairs(slash_separated_groups) do local retfield = defslot and "def" or i == 1 and "indef" or "def" if #slash_separated_group == 1 and slash_separated_group[1] == "" then ret[retfield] = false else local colon_separated_groups = put.split_alternating_runs_and_strip_spaces(slash_separated_group, ":") local specs = {} for _, colon_separated_group in ipairs(colon_separated_groups) do local form = colon_separated_group[1] if form == "" then parse_err(("Use - to indicate an empty ending for %s: '%s'"):format(spectype, table.concat(segments))) elseif form == "-" then form = "" elseif form == "--" then -- missing form = "-" end local new_spec = { form = form, footnotes = fetch_footnotes(colon_separated_group, parse_err) } for _, existing_spec in ipairs(specs) do if existing_spec.form == new_spec.form then parse_err("Duplicate " .. spectype .. " spec '" .. table.concat(colon_separated_group) .. "'") end end table.insert(specs, new_spec) end ret[retfield] = specs end end return ret end --[=[ Parse a single override spec (e.g. 'dat-:i/-' or 'nompl+accpl^/' or 'defnompl+defaccpl!sumrin[when referring to summers in general]:!sumurin[when referring to a specific number of summers]') and return two values: the slot(s) the override applies to, and an object describing the override spec. The input is actually a list where the footnotes have been separated out; for example, given the third example spec above, the input will be a list {"defnompl+defaccpl!sumrin", "[when referring to summers in general]", ":!sumurin", "[when referring to a specific number of summers]", ""}. The object returned for 'dat-:i[mostly in the context of violent actions]/-' looks like this: { indef = { { form = "" }, { form = "i", footnotes = {"[mostly in the context of violent actions]"} } }, def = { { form = "" } } } The object returned for '!nompl+accpl^/' looks like this: { indef = { { form = "^" }, }, def = false } The object returned for 'defnompl+defaccpl!sumrin[when referring to summers in general]:!sumurin[when referring to a specific number of summers]' looks like this: { def = { { form = "!sumrin", footnotes = {"[when referring to summers in general]"} }, { form = "!sumurin", footnotes = {"[when referring to a specific number of summers]"} } } } ]=] local function parse_override(segments, parse_err) local part = segments[1] local slots = {} local defslot while true do local this_defslot if part:find("^def") then this_defslot = true part = usub(part, 4) else this_defslot = false end if defslot == nil then defslot = this_defslot elseif defslot ~= this_defslot then parse_err(("When multiple slot overrides are combined with +, all must be definite or indefinite: '%s'"): format(table.concat(segments))) end local case = usub(part, 1, 3) if case_set[case] then -- ok else parse_err(("Unrecognized case '%s' in override: '%s'"):format(case, table.concat(segments))) end part = usub(part, 4) local slot = defslot and "def_" or "" if part:find("^pl") then part = usub(part, 3) slot = slot .. case .. "_p" else slot = slot .. case .. "_s" end table.insert(slots, slot) if part:find("^%+") then part = usub(part, 2) else break end end segments[1] = part local retval = fetch_slot_override(segments, ("%s slot override"):format(table.concat(slots, "+")), false, defslot, parse_err) return slots, retval end local function parse_adjspec(_base, spec, parse_err) local ret = {} local origspec = spec if spec:find("^:") then ret.lemma = spec:sub(2) elseif spec:find("^/") then local from, to = spec:match("^/([^/]*)/([^/]*)$") if from then ret.subspec = { from = from, to = to } else to = spec:match("^/([^/]*)$") if to then ret.subspec = { to = to } else parse_err(("Syntax error in adjective spec 'adj%s': too many slashes"):format(origspec)) end end elseif spec ~= "" then parse_err(("Syntax error in adjective spec 'adj%s'; should be followed only by a colon + lemma or slash " .. "substitution spec, possibly preceded by ^ to indicate lowercasing"):format(origspec)) end return ret end local function parse_inside(base, inside, is_scraped_noun) local function parse_err(msg) error((is_scraped_noun and "Error processing scraped noun spec: " or "") .. msg .. ": <" .. inside .. ">") end local segments = put.parse_balanced_segment_run(inside, "[", "]") local dot_separated_groups = split_alternating_runs_with_escapes(segments, "%.") local isadj = false for i, dot_separated_group in ipairs(dot_separated_groups) do -- Parse a control spec such as "umut,uUmut[rare]" or "-unuUmut,unuUmut" or "imut". This assumes the control -- spec is contained in `dot_separated_group` (already split on brackets) and the result of parsing should go in -- `base[dest]`. `allowed_specs` is a list of the allowed control specs in this group, such as -- {"umut", "Umut", "uumut", "uUmut", "uUUmut", "u_mut"} or {"con", "-con"}. The result of parsing is a list of -- structures of the form { -- form = "FORM", -- footnotes = nil or {"FOOTNOTE", "FOOTNOTE", ...}, -- }. local function parse_control_spec(dest, allowed_specs) if base[dest] then parse_err(("Can't specify '%s'-type control spec twice; second such spec is '%s'"):format( dest, table.concat(dot_separated_group))) end base[dest] = {} local comma_separated_groups = split_alternating_runs_with_escapes(dot_separated_group, ",") for _, comma_separated_group in ipairs(comma_separated_groups) do local specobj = {} local spec = comma_separated_group[1] if not m_table.contains(allowed_specs, spec) then parse_err(("For '%s'-type control spec, saw unrecognized spec '%s'; valid values are %s"): format(dest, spec, generate_list_of_possibilities_for_err(allowed_specs))) else specobj.form = spec end specobj.footnotes = fetch_footnotes(comma_separated_group, parse_err) table.insert(base[dest], specobj) end end local part = dot_separated_group[1] while true do if i == 1 and not part:find("^adj") and not part:find("^@") and part ~= "builtin" then local comma_separated_groups = split_alternating_runs_with_escapes(dot_separated_group, ",") if #comma_separated_groups > 3 then parse_err(("At most three comma-separated specs are allowed but saw %s"):format( #comma_separated_groups)) end if comma_separated_groups[1][2] then parse_err("Footnotes not allowed on gender indicator") end base.gender = comma_separated_groups[1][1] if not base.gender:find("^[mfn]$") then parse_err(("Unrecognized gender '%s', should be 'm', 'f' or 'n'"):format(base.gender)) end if comma_separated_groups[2] then base.gens = fetch_slot_override(comma_separated_groups[2], "genitive", true, false, parse_err) end if comma_separated_groups[3] then base.pls = fetch_slot_override(comma_separated_groups[3], "nominative plural", true, false, parse_err) end break elseif part == "" then if not dot_separated_group[2] then parse_err("Blank indicator; not allowed without attached footnotes") end base.footnotes = fetch_footnotes(dot_separated_group, parse_err) break elseif part == "addnote" then local spec_and_footnotes = fetch_footnotes(dot_separated_group, parse_err) if #spec_and_footnotes < 2 then parse_err("Spec with 'addnote' should be of the form 'addnote[SLOTSPEC][FOOTNOTE][FOOTNOTE][...]'") end local slot_spec = table.remove(spec_and_footnotes, 1) local slot_spec_inside = rmatch(slot_spec, "^%[(.*)%]$") if not slot_spec_inside then parse_err("Internal error: slot_spec " .. slot_spec .. " should be surrounded with brackets") end local slot_specs = rsplit(slot_spec_inside, ",") -- FIXME: Here, [[Module:it-verb]] called strip_spaces(). Generally we don't do this. Should we? table.insert(base.addnote_specs, { slot_specs = slot_specs, footnotes = spec_and_footnotes }) break elseif ulen(part) > 3 and case_set[usub(part, 1, 3)] or ( ulen(part) > 6 and usub(part, 1, 3) == "def" and case_set[usub(part, 4, 6)]) then local slots, override = parse_override(dot_separated_group, parse_err) for _, slot in ipairs(slots) do if base.overrides[slot] then error(("Two overrides specified for slot '%s'"):format(slot)) else base.overrides[slot] = override end end break end if isadj then if m_is_adjective.parse_for_control_specs(part, parse_control_spec) then break end else if part:find("^[Uu]+_?mut") then parse_control_spec("umut", com.umut_types) break elseif not part:find("^imutval") and part:find("^%-?imut") then parse_control_spec("imut", { "imut", "-imut" }) break elseif part:find("^%-?un[uU]+_?mut") then local unumut_types_and_negated = {} for _, typ in ipairs(com.unumut_types) do table.insert(unumut_types_and_negated, typ) table.insert(unumut_types_and_negated, "-" .. typ) end parse_control_spec("unumut", unumut_types_and_negated) break elseif not part:find("^unimutval") and part:find("^%-?unimut") then parse_control_spec("unimut", { "unimut", "-unimut" }) break elseif part:find("^%-?con") then parse_control_spec("con", { "con", "-con" }) break elseif part:find("^%-?defcon") then parse_control_spec("defcon", { "defcon", "-defcon" }) break elseif not part:find("^já") and part:find("^%-?j") then -- don't trip over .já indicator parse_control_spec("j", { "j", "-j" }) break elseif not part:find("^vstem") and part:find("^%-?v") then parse_control_spec("v", { "v", "-v" }) break end end if #dot_separated_group > 1 then parse_err( ("Footnotes only allowed with slot overrides, negatable indicators and by themselves: '%s'"): format(table.concat(dot_separated_group))) elseif part:find("^adj") then if i > 1 then parse_err("Adjective spec must be the first indicator") end if base.adjspec then parse_err("Can't specify two adjective specs") end isadj = true base.adjspec = parse_adjspec(base, part:sub(4), parse_err) break elseif part:find("^[mfn]$") then if base.gender then parse_err("Can't specify gender twice") end base.gender = part break elseif not isadj and (part:find("^decllemma%s*:") or part:find("^declgender%s*:") or part:find("^declnumber%s*:")) then local field, value = part:match("^(decl[a-z]+)%s*:%s*(.+)$") if not value then parse_err(("Syntax error in decllemma/declgender/declnumber indicator: '%s'"):format(part)) end if base[field] then parse_err(("Can't specify '%s:' twice"):format(field)) end base[field] = value break elseif part:find("^q%s*:") or part:find("header%s*:") then local field, value = part:match("^(q)%s*:%s*(.+)$") if not value then field, value = part:match("^(header)%s*:%s*(.+)$") end if not value then parse_err(("Syntax error in q/header indicator: '%s'"):format(part)) end if base[field] then parse_err(("Can't specify '%s:' twice"):format(field)) end base[field] = value break elseif not isadj and part:find("^@") then -- FIXME: Implement adjective scraping if base.scrape_spec then parse_err("Can't specify scrape directive '@...' twice") end if part:find(":") then base.scrape_is_suffix, base.scrape_spec, base.scrape_id = part:match("^@(%-?)(.-)%s*:%s*(.+)$") else base.scrape_is_suffix, base.scrape_spec = part:match("^@(%-?)(.-)$") end -- If we saw a hyphen, set `scrape_is_suffix` to true, otherwise false base.scrape_is_suffix = base.scrape_is_suffix == "-" if not base.scrape_spec or base.scrape_spec == "" then parse_err(("Syntax error in scrape directive '%s"):format(part)) end local scrape_init, scrape_rest = rmatch(base.scrape_spec, "^(.)(.*)$") local lower_scrape_init = ulower(scrape_init) if ulower(scrape_init) ~= scrape_init then base.scrape_is_uppercase = true base.scrape_spec = lower_scrape_init .. scrape_rest end break elseif part:find(":") then local spec, value = part:match("^([a-z]+)%s*:%s*(.+)$") if not spec then parse_err(("Syntax error in indicator with value, expecting alphabetic slot or stem/lemma " .. "override indicator: '%s'"):format(part)) end local stem_set = isadj and m_is_adjective.overridable_stem_set or overridable_stem_set if not stem_set[spec] then parse_err(("Unrecognized stem override indicator '%s', should be %s"):format( part, generate_list_of_possibilities_for_err( isadj and m_is_adjective.overridable_stems or overridable_stems))) end if base[spec] then if spec == "stem" then parse_err("Can't specify spec for 'stem:' twice (including using 'stem:' along with # or ##)") else parse_err(("Can't specify '%s:' twice"):format(spec)) end end base[spec] = value break elseif part == "#" or part == "##" then if base.stem then parse_err("Can't specify a stem spec ('stem:', # or ##) twice") end base.stem = part break elseif part == "sg" or part == "pl" or part == "both" then if base.number then if base.number ~= part then parse_err("Can't specify '" .. part .. "' along with '" .. base.number .. "'") else parse_err("Can't specify '" .. part .. "' twice") end end base.number = part break elseif part == "indef" or part == "def" or part == "bothdef" then if base.definiteness then if base.definiteness ~= part then parse_err(("Can't specify two conflicting definiteness values; saw '%s' (%s) when existing " .. "definiteness is %s"):format(part, definiteness_code_to_desc[part], definiteness_code_to_desc[base.definiteness])) else parse_err("Can't specify '" .. part .. "' twice") end end base.definiteness = part break elseif not isadj and (part == "weak" or part == "iending" or part == "rstem" or part == "já" or part == "linkasis") or isadj and (m_is_adjective.boolean_property_set[part] or part == "iscomp") or part == "proper" or part == "common" or part == "dem" or part == "builtin" or part == "indecl" or part == "decl?" then if base.props[part] then parse_err("Can't specify '" .. part .. "' twice") end base.props[part] = true break elseif part == "~" then if base.link_lowercase then parse_err("Can't specify '~' twice") end base.link_lowercase = true break elseif isadj and m_table.contains(com.unumut_types, part) then if base.adj_unumut then parse_err("Can't specify two values for reverse u-mutation spec with adjectives") end base.adj_unumut = part break end parse_err("Unrecognized indicator '" .. part .. "'") end end return base end -- Set some defaults (e.g. number and definiteness) now, because they (esp. the number) may be needed -- below when determining how to merge scraped and user-specified properies. local function set_early_base_defaults(base) if is_regular_noun(base) then local function check_err(msg) error(("Lemma '%s': %s"):format(base.lemma, msg)) end if not base.gender then check_err("Internal error: For nouns, gender must be specified") end base.number = base.number or is_proper_noun(base, base.lemma) and "sg" or base.gender == "m" and (base.lemma:find("skapur$") or base.lemma:find("naður$")) and not base.stem and "sg" or "both" base.definiteness = base.definiteness or is_proper_noun(base, base.lemma) and "indef" or "bothdef" process_declnumber(base) base.actual_gender = base.gender if base.declgender then if not base.declgender:find("^[mfn]$") then check_err(("Unrecognized gender '%s' for 'declgender:', should be 'm', 'f' or 'n'"):format( base.declgender)) end base.gender = base.declgender end end end local function parse_inside_and_merge(inside, lemma, scrape_chain) local function parse_err(msg) error(msg .. ": <" .. inside .. ">") end if #scrape_chain >= 10 then local linked_scrape_chain = {} for _, element in ipairs(scrape_chain) do table.insert(linked_scrape_chain, "[[" .. element .. "]]") end parse_err(("Probable infinite loop in scraping; scrape chain is [[%s]] -> %s"):format(lemma, table.concat(linked_scrape_chain, " -> "))) end local base = create_base() base.lemma = lemma base.scrape_chain = scrape_chain parse_inside(base, inside, #scrape_chain > 0) if not base.scrape_spec then -- If we're not scraping the declension from another noun, just return the parsed `base`. -- But don't set early defaults if we're being scraped because it interferes with overriding the number -- and/or definiteness by the noun that is scraping us. if #scrape_chain == 0 then set_early_base_defaults(base) end return base else local retval = com.find_inflection_given_scrape_spec { lemma = lemma, scrape_spec = base.scrape_spec, scrape_is_suffix = base.scrape_is_suffix, scrape_is_uppercase = base.scrape_is_uppercase, infltemp = "is-ndecl", allow_empty_infl = false, inflid = base.scrape_id, parse_off_ending = com.parse_off_final_nom_ending, } local prefix, base_noun, declspec, errmsg = retval.prefix, retval.base_lemma, retval.infl, retval.errmsg if errmsg then base.prefix = prefix base.base_noun = base_noun base.scrape_error = errmsg return base end -- Parse the inside spec from the scraped noun (merging any sub-scraping specs), and copy over the -- user-specified properties on top of it. table.insert(scrape_chain, base_noun) local inner_base = parse_inside_and_merge(declspec.infl, base_noun, scrape_chain) inner_base.lemma = lemma inner_base.prefix = prefix inner_base.base_noun = base_noun -- Add `prefix` to a full variant of the base noun (e.g. a stem spec or full override). We may need -- to adjust the variant to take into account the base noun being a suffix and/or uppercase (e.g. when -- we use [[-dómur]] to generate the inflection of [[vísdómur]] or [[Björn]] to generate the inflection -- of [[Ásbjörn]]). local function add_prefix(form) if base.scrape_is_suffix then form = form:gsub("^%-", "") end if base.scrape_is_uppercase then local first, rest = rmatch(form, "^(.)(.*)$") if first then form = ulower(first) .. rest end end return prefix .. form end -- If there's a prefix, add it now to all the full overrides in the scraped noun, as well as 'decllemma' -- and all stem overrides. if prefix ~= "" then map_all_overrides(inner_base, function(formobj) -- Not if the override contains # or ##, which expand to the full lemma (possibly minus -r -- or -ur). if formobj.form:find("^!") and not formobj.form:find("#") then formobj.form = "!" .. add_prefix(usub(formobj.form, 2)) end end) if inner_base.decllemma then inner_base.decllemma = add_prefix(inner_base.decllemma) end for _, stem in ipairs(overridable_stems) do -- Only actual stems, not (un)imutval; and not if the stem contains # or ##, which -- expand to the full lemma (possibly minus -r or -ur). if inner_base[stem] and stem:find("stem$") and not inner_base[stem]:find("#") then inner_base[stem] = add_prefix(inner_base[stem]) end end end local function copy_properties(plist) -- Copy various properties. for _, prop in ipairs(plist) do if base[prop] ~= nil then inner_base[prop] = base[prop] end end end copy_properties(control_specs) copy_properties(overridable_stems) copy_properties { "gens", "pls", "gender", "number", "definiteness", "decllemma", "declgender", "declnumber", "q", "header", "link_lowercase" } inner_base.footnotes = iut.combine_footnotes(inner_base.footnotes, base.footnotes) -- Copy addnote specs. for _, prop_list in ipairs { "addnote_specs" } do for _, prop in ipairs(base[prop_list]) do m_table.insertIfNot(inner_base[prop_list], prop) end end -- Now copy remaining user-specified specs into the scraped noun `base`. for _, prop_table in ipairs { "overrides", "props" } do for slot, prop in pairs(base[prop_table]) do inner_base[prop_table][slot] = prop end end -- Now determine the defaulted number and definiteness (after copying relevant settings -- but before the check just below that relies on `inner_base.number` being set). set_early_base_defaults(inner_base) -- If user specified 'sg', cancel out any pl overrides, otherwise we'll get an error. if inner_base.number == "sg" then inner_base.pls = nil for slot, _ in pairs(inner_base.overrides) do if slot:find("_p$") then inner_base.overrides[slot] = nil end end end return inner_base end end --[=[ Parse an indicator spec (text consisting of angle brackets and zero or more dot-separated indicators within them). Return value is an object of the form indicated in the comment above create_base(). ]=] local function parse_indicator_spec(angle_bracket_spec, lemma, pagename) if lemma == "" then lemma = pagename end local inside = rmatch(angle_bracket_spec, "^<(.*)>$") assert(inside) local orig_lemma = lemma local orig_lemma_no_links = m_links.remove_links(lemma) lemma = orig_lemma_no_links local base = parse_inside_and_merge(inside, lemma, {}) base.orig_lemma = orig_lemma base.orig_lemma_no_links = orig_lemma_no_links return base end -- Determine if the term has more than one word in it. Normally we just look at the number of words -- at top level. However, it's possible to have a single alternant at top level with multiple words -- in one of the arms, e.g. the equivalent of ((rēspūblica<>,rēs<>pūblica<>)). So if there's only one -- top-level "word" and it's an alternant, check the length of each arm. We also need to check for -- before-text and post-text if there's only one inflected term. local function compute_is_multiword(alternant_multiword_spec) if #alternant_multiword_spec.alternant_or_word_specs > 1 or alternant_multiword_spec.post_text ~= "" then return true end local alternant_or_word_spec = alternant_multiword_spec.alternant_or_word_specs[1] if alternant_or_word_spec.alternants then for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do if #multiword_spec > 1 or multiword_spec.post_text ~= "" or multiword_spec[1] and multiword_spec[1].before_text ~= "" then return true end end end if alternant_or_word_spec.before_text ~= "" then return true end return false end local function set_defaults_and_check_bad_indicators(base) local function check_err(msg) error(("Lemma '%s': %s"):format(base.lemma, msg)) end -- Set default values. local regular_noun = is_regular_noun(base) if not base.adjspec and base.props.builtin then set_builtin_defaults(base) end if not regular_noun and not base.adjspec then for _, control_spec in ipairs(control_specs) do if base[control_spec] then check_err(("'%s' cannot be specified with pronouns"):format(control_spec)) end end end if not regular_noun then if base.declgender then check_err("'declgender' can only be specified with regular nouns") end return end -- Check for bad indicator combinations. if base.imut and base.unimut then check_err("'imut' and 'unimut' specs cannot be specified together") end if base.umut and base.unumut then check_err("'umut' and 'unumut' specs cannot be specified together") end if base.unimut and base.unumut then check_err("'unimut' and 'unumut' specs cannot be specified together") end if base.declnumber == "pl" and (base.gens or base.pls) then check_err("Cannot set genitive or plural specs after the gender in plural-only lemmas") end if base.plvstem and not base.plstem then check_err("When 'plvstem:' given, 'plstem:' must also be given") end -- Compute whether i-mutation stems are needed. -- First check for 'imut' set by user. if not base.need_imut then -- might be set by the detected declension if base.imut then for _, formobj in ipairs(base.imut) do if formobj.form == "imut" then base.need_imut = true break end end end end -- Then check for 'unimut' set by user. if not base.need_imut then if base.unimut then for _, formobj in ipairs(base.unimut) do if formobj.form == "unimut" then base.need_imut = true break end end end end -- Then check all overrides for any beginning with a single ^. if not base.need_imut then map_all_overrides(base, function(formobj) if formobj.form:find("^%^") and not formobj.form:find("^%^%^") then base.need_imut = true return true end end) end if base.imutval and not base.need_imut then check_err("'imutval:...' specified but 'imut' and 'unimut' not specified and no forms need i-mutation") end if base.unimutval and not base.need_imut then check_err("'unimutval:...' specified but 'imut' and 'unimut' not specified and no forms need i-mutation") end end local function set_all_defaults_and_check_bad_indicators(alternant_multiword_spec) -- Used when determining how to link definite-only and plural-only nouns. alternant_multiword_spec.is_multiword = compute_is_multiword(alternant_multiword_spec) iut.map_word_specs(alternant_multiword_spec, function(base) base.is_multiword = alternant_multiword_spec.is_multiword set_defaults_and_check_bad_indicators(base) for _, global_prop in ipairs { "q", "header" } do if base[global_prop] then if alternant_multiword_spec[global_prop] == nil then alternant_multiword_spec[global_prop] = base[global_prop] elseif alternant_multiword_spec[global_prop] ~= base[global_prop] then error(("With multiple words or alternants, set '%s' on only one of them or make them all agree"): format(global_prop)) end end end if base.props.builtin then alternant_multiword_spec.saw_builtin = true else alternant_multiword_spec.saw_non_builtin = true end if base.props.indecl then alternant_multiword_spec.saw_indecl = true else alternant_multiword_spec.saw_non_indecl = true end if base.props["decl?"] then alternant_multiword_spec.saw_unknown_decl = true else alternant_multiword_spec.saw_non_unknown_decl = true end end) end local function expand_property_sets(base) base.prop_sets = { {} } -- Construct the prop sets from all combinations of control specs, in case any given spec has more than one -- possibility. for _, control_spec in ipairs(control_specs) do local specvals = base[control_spec] -- Handle unspecified control specs. if not specvals then specvals = { false } end if #specvals == 1 then for _, prop_set in ipairs(base.prop_sets) do -- Convert 'false' back to nil prop_set[control_spec] = specvals[1] or nil end else local new_prop_sets = {} for _, prop_set in ipairs(base.prop_sets) do for _, specval in ipairs(specvals) do local new_prop_set = m_table.shallowCopy(prop_set) new_prop_set[control_spec] = specval table.insert(new_prop_sets, new_prop_set) end end base.prop_sets = new_prop_sets end end end -- Return the most likely ending to add to a stem form (e.g. from the feminine singular or the plural) to -- to get the lemma form (masculine singular). -- We use the following rules: -- 1. Stems in -nn or -ll take -ur. -- 2. Stems in -Vn or -Vl double the last letter, but not if this will result in contraction (the default -- for nouns in -[aiu]nn and -[aiu]ll, but for adjectives only in -inn), in which case -ur is added. -- 3. Stems in -Cn, -Cl, -r or -s remain unchanged. -- 4. Stems in a vowel add -r. -- 5. Remaining stems add -ur. -- Exceptional lemma forms for adjectives will need to be handled through a slash substitution spec or by -- directly specifying the lemma after a colon. local function stem_to_masc_sg_lemma_ending(stem, isadj) if stem:find("nn$") or stem:find("ll$") then return "ur" elseif not isadj and (stem:find("a[nl]$") or stem:find("[^eE]i[nl]$") or stem:find("[^aA]u[nl]$")) or isadj and stem:find("[^eE]in$") then return "ur" elseif stem:find(com.vowel_c .. "[nl]$") then return stem:sub(-1) elseif stem:find("[nlrs]$") then return "" elseif rfind(stem, com.vowel_c .. "$") then return "r" else return "ur" end end -- For a plural-only lemma, synthesize a likely singular lemma. It doesn't have to be theoretically correct as long as -- it generates all the correct plural forms. local function synthesize_singular_lemma(base) local lemma_determined -- Loop over all property sets in case the user specified multiple ones (e.g. using different control specs). If -- we try to reconstruct different lemmas for different property sets, we'll throw an error below. for _, props in ipairs(base.prop_sets) do local function interr(msg) error(("Internal error: For lemma '%s', %s: %s"):format(base.lemma, msg, dump(props))) end -- `ending` refers to the plural ending but is not currently used much. Instead, in add_decl(), when we process -- pl-only terms, we set the nom_pl to "*" so that the lemma is used directly. local stem, lemma, ending, sg_ending, default_unumut if base.gender == "m" or base.gender == "f" then stem, ending = rmatch(base.lemma, "^(.*)([aiu]r)$") if stem then -- masc: -- -- [[tónleikar]] "concert"; [[feðgar]] "father and son"; [[hafrar]] "oats" (dat pl höfrum); -- [[fjármunir]] "goods, property"; [[Fljótsdælir]] "inhabitants of Fljótsdalur (a valley)" -- (occurs definite, needs 'dem', no unimut); [[Ásmegir]] "sons of the Gods" (occurs definite, dat -- pl Ásmögum, gen pl Ásmaga, i.e. needs 'def' and 'unimut'); similarly [[ljóðmegir]]; -- [[buskuleggir]] "?" (has 'j' in dat pl [[buskuleggjum]], gen pl [[buskuleggja]]; [[Bekkir]] -- (place name; has 'j' in dat pl [[Bekkjum]], gen pl [[Bekkja]]) -- -- fem: -- [[frönskur]] "French fries" (with unumut); [[hjólbörur]] "wheelbarrow" (with unumut); [[buxur]] -- "trousers, pants", [[hættur]] "bedtime; quitting time" (with unimut); [[herðar]] "shoulders"; -- [[limar]] "branches"; [[öfgar]] "exaggeration, extreme" (no unumut); [[drefjar]] -- "stains, traces"; [[viðjar]] "chains, fetters"; many others in -jar, but the -j- is throughout -- the plural; [[svalir]] "balcony; porch"; [[dyr]] "doorway" (uses decllemma:dyrir) if ending == "ur" then -- FIXME: Does -ur as masculine plural ending occur? What should the singular be? sg_ending = base.gender == "f" and "a" or nil default_unumut = "unumut" else sg_ending = base.gender == "f" and "" or nil end if not sg_ending then sg_ending = stem_to_masc_sg_lemma_ending(stem) end elseif base.lemma:find("ær$") then -- [[barnatær]], [[fultær]], proper name [[Tær]] stem = base.lemma sg_ending = "" else error(("Masculine or feminine plural-only lemma '%s' should end in -ar, -ir or -ur"):format(base.lemma)) end elseif base.gender == "n" then -- Neuters in -i. Examples: [[fræði]] "branch of knowledge", [[jafndægri]] "equinox", [[meðmæli]] -- "recommendation", [[sannindi]] "truth", [[skæri]] "pair of scissors", [[vísindi]] "knowedge, learning". -- unimut is possible and occurs in [[læti]] "behavior, demeanor", [[ólæti]] "noise, racket". stem, ending = rmatch(base.lemma, "^(.*[^eE])(i)$") if stem then sg_ending = "i" end if not stem then -- Weak neuters in -u like [[gleraugu]] "glasses/spectacles". stem, ending = rmatch(base.lemma, "^(.*[^aA])(u)$") if stem then sg_ending = "a" default_unumut = "unumut" end end if not stem then -- Generally, plural will look like singular, with no ending in the plural (but there will be umut -- if possible). Examples: [[feðgin]] "father and daughter", [[hjón]] "married couple", [[jól]] -- "Christmas", [[lok]] "end"; [[jarðgöng]] "tunnel" needing 'unumut'. stem = base.lemma sg_ending = "" default_unumut = "unumut" end else interr(("unrecognized gender '%s'"):format(base.gender)) end if default_unumut and not props.unumut and not props.umut and not props.unimut then props.unumut = { form = default_unumut, defaulted = true } end if props.unumut and props.unimut then interr("shouldn't see both 'unumut' and 'unimut' set in plural-only lemma") end if props.unumut and props.unumut.form:find("^un") then stem = apply_reverse_u_mutation(stem, props.unumut.form, not props.unumut.defaulted) if props.umut then interr("shouldn't see both 'unumut' and 'umut' set in plural-only lemma") end props.umut = { form = rsub(props.unumut.form, "^un", ""), footnotes = props.unumut.footnotes, defaulted = props.unumut.defaulted } props.unumut = nil end if props.unimut and props.unimut.form:find("^un") then stem = apply_reverse_i_mutation(stem, base.unimutval, "error if unmatchable") if props.imut then interr("shouldn't see both 'unimut' and 'imut' set in plural-only lemma") end props.imut = { form = rsub(props.unimut.form, "^un", ""), footnotes = props.unimut.footnotes } props.unimut = nil end lemma = stem .. sg_ending if lemma_determined and lemma_determined ~= lemma then error(("Attempt to set two different singular lemmas '%s' and '%s'"):format(lemma_determined, lemma)) end lemma_determined = lemma end base.lemma = lemma_determined base.lemma_ending = ending or "" end -- For a nominative definite lemma, synthesize the corresponding indefinite lemma. Note that a plural definite lemma may -- need to be processed twice, first to convert to plural indefinite and then to convert to singular indefinite using -- synthesize_singular_lemma(). local function synthesize_indefinite_lemma(base) local lemma_determined -- Loop over all property sets in case the user specified multiple ones (e.g. using different control specs). If -- we try to reconstruct different lemmas for different property sets, we'll throw an error below. for _, props in ipairs(base.prop_sets) do local function interr(msg) error(("Internal error: For lemma '%s', %s: %s"):format(base.lemma, msg, dump(props))) end -- There are only 6 clitic articles, depending on the combination of gender and number: -- singular: m = -inn, f = -in, n = -ið; plural: m = -nir, f = -nar, n = -in. The two beginning in n- aren't -- problematic because in all cases they simply append to the actual form. The remaining four, however, drop -- the i- before an ending [aiu]. This means we can uniquely reconstruct the dropped vowel if we see e.g. -- -að or -uð in place of -ið. But if we see -ið, we don't know whether the lemma ended in -i or a consonant. -- And in general it's important to know because it affects some forms; e.g. compare definite neuter 'knippið' -- from [[knippi]] "bundle, bunch" with 'klappið' from [[klapp]] "applause; pat, stroke". The former has -- definite genitive 'knippisins' and the latter 'klappsins'. And in fact, all three genders commonly have -- both consonant-ending and i-ending nouns in the singular. This means we need an indicator to distinguish -- them. Probably easiest is 'iending'; reusing 'weak' won't work so well because neuters in -i, and sometimes -- feminines in -i, are considered strong. local function process_n_clitic(clitic) local lemma = base.lemma:match("^(.*)" .. clitic .. "$") if not lemma then error(("Lemma '%s' declared as %s %s should end in clitic '-%s'"):format(base.lemma, gender_code_to_desc[base.gender] or "NONE", number_code_to_desc[base.number] or "NONE", clitic)) end return lemma end local function process_i_clitic(clitic) local clitic_cons_end = clitic:match("^i(.*)$") if not clitic_cons_end then interr(("clitic '%s' should begin with 'i'"):format(clitic)) end local lemma_begin, ending = base.lemma:match("^(.*)([aiu])" .. clitic_cons_end .. "$") if not lemma_begin then error(("Lemma '%s' declared as %s %s should end in clitic '-%s' or in '-a%s' or '-u%s'"):format( base.lemma, gender_code_to_desc[base.gender] or "NONE", number_code_to_desc[base.number] or "NONE", clitic, clitic_cons_end, clitic_cons_end)) end if ending == "a" or ending == "u" then if base.props.iending then error(("Property 'iending' cannot be specified because definite lemma '%s' does not end in '-%s'"): format(base.lemma, clitic)) end return lemma_begin .. ending end if base.props.iending then return lemma_begin .. "i" else return lemma_begin end end local clitic = clitic_articles[base.gender]["nom_" .. (base.number == "pl" and "p" or "s")] local lemma if clitic:find("^n") then lemma = process_n_clitic(clitic) else lemma = process_i_clitic(clitic) end if lemma_determined and lemma_determined ~= lemma then error(("Attempt to set two different indefinite lemmas '%s' and '%s'"):format(lemma_determined, lemma)) end lemma_determined = lemma end base.lemma = lemma_determined base.lemma_ending = "" end -- For an adjectival lemma, synthesize the masc singular form. local function synthesize_adj_lemma(base) if base.props.indecl then base.decl = "indecl" return elseif base.props["decl?"] then base.decl = "decl?" return else base.decl = "adj" local adjspec = base.adjspec if not adjspec then error("Internal error: synthesize_adj_lemma() called without a parsed adjective spec in `base.adjspec`") end if adjspec.lemma then base.lemma = adjspec.lemma elseif adjspec.subspec then local from, to = adjspec.subspec.from, adjspec.subspec.to if from then local beginpart = base.lemma:match("^(.*)" .. m_string_utilities.pattern_escape(from) .. "$") if not beginpart then error(("Adjective slash substitution spec '/%s/%s' didn't match form '%s'"):format( from, to, base.lemma)) end base.lemma = beginpart .. to else local num_to_remove if base.number == "pl" then num_to_remove = (base.gender == "m" or base.gender == "f") and 2 or 0 elseif base.gender == "m" then error(("Single-part adjective slash substitution spec '/%s' not allowed with masculine-singular " .. "adjective form '%s'; if necessary, use a two-part spec or specify the lemma directly after " .. " a colon"):format(from, base.lemma)) else num_to_remove = base.gender == "f" and 0 or 1 end base.lemma = usub(base.lemma, 1, -num_to_remove - 1) .. to end else local stem, stem_is_lemma if base.props.iscomp then base.adj_is_weak = true if base.number ~= "pl" and base.gender == "n" then stem = base.lemma:match("^(.*)a$") if not stem then error(("Neuter singular weak comparative adjective form should end in -a: %s" ):format(base.lemma)) end stem = stem .. "i" else if not base.lemma:find("i$") then error(("Plural or masculine/feminine singular weak comparative adjective form should " .. "end in -i: %s"):format(base.lemma)) end stem = base.lemma end stem_is_lemma = true elseif base.number == "pl" then stem = base.lemma:match("^(.*[^Aa])u$") if stem then base.adj_is_weak = true end if not stem then if base.gender == "m" then stem = base.lemma:match("^(.*)ir$") if not stem then error(("Masculine plural strong adjective form should end in -ir: %s"):format(base.lemma)) end elseif base.gender == "f" then stem = base.lemma:match("^(.*)ar$") if not stem then error(("Feminine plural strong adjective form should end in -ar: %s"):format(base.lemma)) end else stem = base.lemma end end else if base.gender == "m" then stem = base.lemma:match("^(.*[^Ee])i$") if stem then base.adj_is_weak = true else -- Otherwise the form is strong and should remain as is. stem = base.lemma stem_is_lemma = true end elseif base.gender == "f" or base.gender == "n" then stem = base.lemma:match("^(.*)a$") if stem then base.adj_is_weak = true end if not stem then if base.gender == "n" then error(("No automatic rules for inferring the adjective lemma from strong neuter form " .. "'%s'; you must use a slash substitution spec such as 'tvöfalt<adj/dur>' (which " .. "chops off the last letter and replaces it with 'dur') or 'ryðfrítt<adj/tt/r>' " .. "(which chops off 'tt' and replaces it with 'r'), or directly specify the lemma " .. "using e.g. 'tvö<adj:tveir>'" ):format(base.lemma)) else stem = base.lemma end end end end if not stem_is_lemma then if base.adj_unumut then stem = apply_reverse_u_mutation(stem, base.adj_unumut, "error if unmatchable") end base.lemma = stem .. stem_to_masc_sg_lemma_ending(stem) else base.lemma = stem end end end end -- Determine the declension and stem based on the lemma and gender. The declension is set in base.decl and the stem in -- base.stem if not already set by the user. local function determine_declension(base) local stem = nil local ending local default_props = {} -- Determine declension if base.props.indecl then base.decl = "indecl" stem = base.lemma elseif base.props["decl?"] then base.decl = "decl?" stem = base.lemma elseif base.gender == "m" then if not stem then stem, ending = rmatch(base.lemma, "^(.*[ÁáÆæ])(r)$") if stem then -- in -ár: -- [[nár]] "corpse", [[sár]] "tub (archaic)", [[hár]] "thole, oarlock (archaic)", [[hár]] -- "spiny dogfish (archaic)" (with v-infix), [[kljár]] "weaving stone (archaic)", [[ljár]] "scythe", -- [[skjár]] "video screen, display", [[már]] "seagull", [[sjár]] "sea", [[snjár]] "snow" (with -- v-infix); vs. stems ending in -r: [[ár]] "? (archaic)", [[lár]] "wooden box for wool", [[klár]] -- "inferior horse, nag", [[pílár]] "slat, fence post; spoke (dated)", [[kentár]] "centaur" -- -- in -ær: -- [[glær]] "sea", [[skær]] "? (obsolete)", [[blær]] "gentle breeze", [[bær]] "farm; town", [[óbær]] -- "?", [[sær]] "sea", [[snær]] "snow" -- -- We used to also drop -r by default from the stem in -ýr words, but this doesn't make sense for -- adjectives and only half makes sense for nouns, so we don't do it any more. We have stems without -r: -- [[ýr]] "yew", [[býr]] "town, farm", [[gnýr]] "clash, rumble; blue wildebeest", [[týr]] "hero; god", -- also many proper names; vs. stems ending in -r: [[fýr]] "dude, guy", [[lýr]] "pollock", [[sýr]] -- "? (poetic)", [[ýr]] "? (obsolete)", [[hlýr]] "? (obsolete)", [[glýr]] "? (obsolete)" base.decl = "m" end end if not stem then stem = rmatch(base.lemma, "^(.*[Aa]ur)$") if stem then -- [[maur]] "ant", [[aur]] "loam, mud", [[gaur]] "ruffian", [[paur]] "devil; enmity", -- [[saur]] "dirt; excrement", [[staur]] "post", [[ljósastaur]] "lamp post" base.decl = "m" end end if not stem then -- There must be at least one vowel; lemmas like [[bur]] don't count. stem, ending = rmatch(base.lemma, "^(.*" .. com.vowel_or_hyphen_c .. ".*)(ur)$") if stem then if stem:find("skap$") and not base.stem then -- tons of words in -skapur base.decl = "m-skapur" elseif stem:find("nað$") and not base.stem then -- lots of words in -naður base.decl = "m-naður" default_props.umut = "uUmut" else if base.stem == base.lemma then -- [[akur]] "field" etc. where the stem includes the final -r stem = base.stem ending = "" -- not actually used default_props.con = "con" end -- [[hestur]] "horse" and lots of others base.decl = "m" end end end if not stem then stem = rmatch(base.lemma, "^(.*[Ee][iy]r)$") if stem then -- in -eir (all include -r in the stem): -- [[geir]] "?", [[eir]] "copper", [[leir]] "clay", [[Geir]] (male given name) -- -- in -eyr, including -r in the stem: -- [[reyr]] "reed", [[Reyr]] (male given name) -- in -eyr, not including -r in the stem: -- [[þeyr]] "thaw, thawing wind", [[Þeyr]] (male given name), [[Freyr]] (male given name) base.decl = "m" end end if not stem then -- There must be at least one vowel (although there don't appear to be any single-syllable -- lemmas ending in -ir other than in -eir). stem, ending = rmatch(base.lemma, "^(.*" .. com.vowel_or_hyphen_c .. ".*)(ir)$") if stem then -- [[læknir]] "physician" and many others -- [[bróðir]], [[faðir]] are r-stems if base.props.rstem then base.decl = "m-rstem" base.need_imut = true else base.decl = "m-ir" end end end if not stem then stem, ending = rmatch(base.lemma, "^(.*l)(l)$") if stem then if is_proper_noun(base, stem) and stem:find("kel$") then base.decl = "m-kell" else if not base.stem and (rfind(stem, com.cons_c .. "[aiu]l$") or stem:find("^[aiuAIU]l$")) then -- [[gaffall]] "fork" (dat pl [[göfflum]]), [[þumall]] "thumb"; [[ekkill]] "widower"; -- [[spegill]] "mirror"; [[segull]] "magnet"; [[öxull]] "axis; axle"; etc. Note that the check -- for a consonant preceding the a/i/u is important as there are words like [[manúall]] -- "manual", [[ritúall]] "ritual", [[kokteill]] "cocktail", [[feill]] "flaw, error", [[deill]] -- "dispute???" (rare, regional), [[haull]] "hernia", [[straull]] "? (rare, regional)" that -- don't have contraction. Beware of the rare word [[síill]] "sieve? strainer?" that per BÍN -- does contract to síl- before vowels. Currently the code to handle contraction will throw an -- error if you attempt to contract that word, but you can use 'vstem:...'. -- -- There are also lots of words in a vowel other than a/i/u followed by -ll, such as [[bíll]] -- "car", [[áll]] "eel", [[konsúll]] "consul", [[þræll]] "slave", [[hvoll]] "hill", [[stóll]] -- "chair", etc. In these, the final -l is the nominative singular ending, as above. -- -- Note that if the user overrode the stem (e.g. using '#' as with [[Ármann]]), we don't -- default to contraction as it may cause an error to be thrown. default_props.con = "con" end base.decl = "m" end end end if not stem then stem, ending = rmatch(base.lemma, "^(.*n)(n)$") if stem then if not base.stem and (rfind(stem, com.cons_c .. "[aiu]n$") or stem:find("^[aiuAIU]n$")) then -- As with -all/-ill/-ull although there are fewer such words in -nn. Examples: [[aftann]] "evening" -- (dat pl öftnum), [[arinn]] "hearth, fireplace" (dat pl örnum), [[drottinn]] "lord", [[himinn]] -- "sky, heaven", [[morgunn]] "morning", [[jötunn]] "giant", etc. -- -- There are also lots of words in a vowel other than a/i/u followed by -nn, such as [[fleinn]] -- "spear", [[steinn]] "rock", [[prjónn]] "knitting needle", [[daunn]] "stink", [[húnn]] "knob". -- In these, the final -n is the nominative singular ending, as above. -- -- Note that if the user overrode the stem (e.g. using '#' as with [[Ármann]]), we don't default -- to contraction as it may cause an error to be thrown. default_props.con = "con" end base.decl = "m" end end if not stem and not base.props.weak then stem, ending = rmatch(base.lemma, "^(.*[aóæ]nd)(i)$") if stem then -- [[nemandi]] "student" and many others; terms in -jandi like [[byrjandi]] -- "beginner", [[seljandi]] "seller" umlaut to -jend- in the plural instead of -ind- -- also terms in -óndi (probably all compounds of [[bóndi]] "farmer") and in -ændi -- (probably all compounds of [[frændi]]). Terms like [[andi]] "breath, spirit" and -- [[heiðasandi]] "heath sand?" need '.weak' to disable this, as does [[fjandi]] in -- the meaning "devil, demon" (vs. "enemy", which has plural [[fjendur]]). Terms like -- [[vandi]] "trouble; responsibility; custom, habit" and compounds are singular-only. base.decl = "m-ndi" if not stem:find("ænd$") then base.need_imut = true end if stem:find("jand$") then default_props.imutval = "je" end end end if not stem then stem, ending = rmatch(base.lemma, "^(.*)([ia])$") if stem then -- [[tími]] "time, hour" and many others; [[herra]] "gentleman" ([[sendiherra]] "ambassador"), -- [[séra]]/[[síra]] "reverend" base.decl = "m-weak" -- Recognize -ingi and make automatically j-infixing, but only when a vowel precedes -- (not [[ingi]], [[Ingi]], [[stingi]], [[þvingi]]). Use `-j` to turn this off. if ending == "i" and rfind(stem, com.vowel_or_hyphen_c .. ".*ing$") then default_props.j = "j" elseif ending == "i" and rfind(stem, com.vowel_or_hyphen_c .. ".*ar$") then default_props.umut = "uUmut" end end end if not stem then stem = rmatch(base.lemma, "^(.*ó)$") if stem then -- [[kanó]] "canoe", [[pesó]] "peso", [[Plútó]] "Pluto", [[Markó]] (male given name), etc. base.decl = "m-ó" end end if not stem then -- Miscellaneous masculine terms without ending stem = base.lemma base.decl = "m" end elseif base.gender == "f" then if not stem then stem, ending = rmatch(base.lemma, "^(.*)(a)$") if stem then base.decl = "f-weak" end end if not stem then stem, ending = rmatch(base.lemma, "^(.*[^eE])(i)$") if stem then base.decl = "f-i" end end if not stem and not base.stem then -- Don't match when base.stem is set, e.g. [[gimbur]] "female lamb", where the -ur is part of the stem stem, ending = rmatch(base.lemma, "^(.*[^aA])(ur)$") if stem and rfind(stem, com.vowel_or_hyphen_c) then if is_proper_noun(base, stem) then -- [[Auður]], [[Heiður]], [[Ingveldur]], [[Móeiður]], [[Þórelfur]], [[-frídur]] ([[Gunnfríður]], -- [[Hólmfríður]], [[Málfríður]], [[Sigfríður]]), [[Gerður]] ([[Hallgerður]], [[Ingigerður]], -- [[Þorgerður]]), [[Gunnur]] ([[Arngunnur]], [[Hildigunnur]]), [[Heiður]] ([[Aðalheiður]], -- [[Arnheiður]], [[Brynheiður]], [[Ragnheiður]]), [[Hildur]] ([[Ásthildur]], [[Berghildur]], -- [[Brynhildur]], [[Geirhildur]], [[Gunnhildur]], [[Ragnhildur]], [[Þórhildur]]), [[Ástríður]] -- (related names [[Guðríður]], [[Sigríður]], [[Þuríður]]), [[Þrúður]] [also a man's name] -- ([[Jarþrúður]], [[Jarðþrúður]], [[Sigþrúður]]) -- -- also with company/organization names like [[Berghildur]], [[Gunnhildur]]; likewise place names -- like [[Þuríður]] base.decl = "f-acc-dat-i" else base.decl = "f-ur" end end end if not stem and base.props.rstem then stem, ending = rmatch(base.lemma, "^(.*[^eE])(ir)$") if stem and base.props.rstem then -- [[dóttir]], [[móðir]], [[systir]] base.decl = "f-rstem" if not stem:find("syst$") then base.need_imut = true end end end if not stem then stem = rmatch(base.lemma, "^(.*ung)$") if stem then base.decl = "f-ung" end end if not stem then stem = rmatch(base.lemma, "^(.*ing)$") if stem then base.decl = "f-ing" end end if not stem then stem = rmatch(base.lemma, "^(.*[áóúÁÓÚ])$") if stem then base.decl = "f-long-vowel" if rfind(stem, "[óÓ]$") then base.need_imut = true end end end if not stem and not base.stem then -- Not when base.stem is set, which includes [[Ýr]], following the regular endingless f declension where -- -r is part of the stem. stem, ending = rmatch(base.lemma, "^(.*[ýÝæÆ])(r)$") if stem then -- [[kýr]] "cow", [[sýr]] "sow (archaic)", [[ær]] "ewe" and compounds base.decl = "f-long-umlaut-vowel-r" default_props.unimut = "unimut" end end if not stem then stem = rmatch(base.lemma, "^(.*[^aA]un)$") if stem and rfind(stem, com.vowel_or_hyphen_c) then -- [[pöntun]] "order (in commerce)"; [[verslun]] "trade, business; store, shop"; [[efun]] "doubt"; -- [[bötun]] "improvement"; [[örvun]] "encouragement; stimulation" (pl. örvanir); etc. -- Exclude words in -aun like [[baun]] "bean", [[laun]] "secret", [[raun]] "experience". -- Some words need a different indicator, e.g. [[örvun]] "encouragement; stimulation" (pl. örvanir), -- [[fjölgun]] "increase, proliferation" (pl. fjölganir), which need "unUmut". base.decl = "f" default_props.unumut = "unuUmut" end end if not stem then -- Miscellaneous feminine terms without ending stem = base.lemma base.decl = "f" -- A function here means we resolve it to its actual value later. We don't want to trigger -- unumut if the user specified v-infix or any type of u-mutation (e.g. 'uUmut' in [[ætlan]]), -- or if the last vowel of the term is 'a' ([[dragt]], [[aukavakt]]). default_props.unumut = function(base, props) if base.vstem or props.v and props.v.form == "v" or props.umut or rfind(stem, "[Aa]" .. com.cons_c .. "*$") then return nil else return { form = "unumut", defaulted = true } end end end elseif base.gender == "n" then if not stem then stem, ending = rmatch(base.lemma, "^(.*)(a)$") if stem then base.decl = "n-weak" end end if not stem then -- stem actually includes -é but due to the change to já we include it in the ending stem, ending = rmatch(base.lemma, "^(.*)(é)$") if stem then if base.props["já"] then -- Indicator 'já' for [[tré]], [[hné]]/[[kné]], etc. base.decl = "n-já" else -- [[té]] (letter T), etc. stem = stem .. "é" base.decl = "n" end end end if not stem then stem, ending = rmatch(base.lemma, "^(.*[kKgG])(i)$") if stem then base.decl = "n-i" default_props.j = "j" end end if not stem then stem, ending = rmatch(base.lemma, "^(.*[^eE])(i)$") if stem then base.decl = "n-i" end end if not stem then -- -ur preceded by a consonant and at least one vowel. stem = rmatch(base.lemma, "^(.*" .. com.vowel_or_hyphen_c .. ".*" .. com.cons_c .. "ur)$") if stem then base.decl = "n" default_props.con = "con" default_props.defcon = "defcon" end end if not stem then -- Miscellaneous neuter terms without ending stem = base.lemma base.decl = "n" end else error(("Internal error: `base.gender` is '%s' but should be 'm', 'f' or 'n'"):dump(base.gender)) end if base.stem then -- This isn't necessarily accurate but doesn't really matter. We only record the lemma ending to help with -- contraction of definite clitics in the nominative singular, and in the cases where the user gives an explicit -- stem, it's usually with # (meaning the ending is null) or ## (meaning the ending ends in -r), and in both -- cases there's no contraction of initial vowels in definite clitics in any case. base.lemma_ending = "" else base.stem = stem base.lemma_ending = ending or "" end for k, v in pairs(default_props) do if not base[k] then if control_spec_set[k] then for _, props in ipairs(base.prop_sets) do if type(v) == "function" then props[k] = v(base, props) else props[k] = { form = v, defaulted = true } end end else base[k] = v end end end track("decl/" .. base.decl) end local function determine_default_masc_dat_sg(base, props) -- We only need to compute the default dative singular for regular masculines (not masculines in -ir, -ó, -i, etc.). -- These other types have specific defaults for the entire type. if base.decl ~= "m" or base.number == "pl" then return end local default_dat_sg if base.overrides.dat_s then -- Track explicit override. track("masc-dat-sg-override") end local stem = base.stem if props.j and props.j.form == "j" then -- Stems with j-infix normally have null dative even if they end in two consonants, e.g. -- [[belgur]] "bellows; skin", [[fengur]] "profit", [[flekkur]] "spot, fleck", [[serkur]] -- "shirt", [[stingur]] "sting" default_dat_sg = "" elseif stem:find(com.vowel_or_hyphen_c .. ".*[iu]ng$") then -- Stems in suffix -ing or -ung normally have indef dat -i, def dat null default_dat_sg = { indef = "i", def = "" } elseif stem:find("x$") or (rfind(stem, com.cons_c .. com.cons_c .. "$") and not stem:find("kk$") and not stem:find("pp$")) then -- Other stems in two consonants normally have dat -i, but those in -kk or -pp normally -- don't, so exclude them and require explicit specification default_dat_sg = "i" elseif not rfind(stem, com.vowel_c .. "$") and is_proper_noun(base, stem) then -- proper noun whose stem does not end in a vowel default_dat_sg = "i" elseif props.con and props.con.form == "con" then default_dat_sg = "i" elseif rfind(stem, com.vowel_c .. "r?$") then default_dat_sg = "" elseif base.lemma:find("ll$") then -- nouns in -ll without contraction, which generally includes those not in -all/-ill/-ull plus a few in these -- endings such as [[panill]] "paneling" (rare variant of [[panell]]), [[kórall]] "coral", [[kristall]] -- "crystal", [[kanill]] "cinnamon" (also [[kanell]]); only a few exceptions, such as [[rafall]] "generator", -- which optionally contracts and has with dati/-:i without contraction; [[hvoll]] "hill", [[kokkáll]] -- "cuckold", [[páll]] "spade, pointed shovel", [[þræll]] "slave" with dat-:i/-; [[hóll]] "hill", [[hæll]] -- "heel", [[stóll]] "chair", which are dat-:i[footnote]/- with a footnote variously indicating that the -- dative in -i occurs only in fixed expressions, compounds, place names, etc. default_dat_sg = "" elseif base.lemma:find("nn$") then -- nouns in -nn without contraction, which generally includes those not in -ann/-inn/-unn; there are fewer of -- these than the corresponding nouns in -ll and they have default dative i/i; only exceptions I can find are -- [[húnn]] "knob", [[tónn]] "tone (music)", [[dúnn]] "down (feathers)"", which have dati:-/i. default_dat_sg = "i" elseif base.overrides.def_dat_s and base.definiteness == "def" then -- OK; user supplied def_dat_s override for a definite-only lemma elseif base.overrides.dat_s and base.overrides.dat_s.indef and base.definiteness == "indef" then -- OK; user supplied dat_s override with indefinite setting, for an indefinite-only lemma elseif base.overrides.dat_s and base.overrides.dat_s.indef and base.overrides.def_dat_s then -- OK; user supplied dat_s override with indefinite setting and def_dat_s override, which -- together provide both indefinite and definite values elseif base.overrides.dat_s and not base.overrides.dat_s.def then error(("Saw masculine stem '%s' and dative singular override of just the indefinite ending, but " .. "requires both the indefinite and definite endings of the dative singular in the form 'datINDEF/DEF'"): format(stem)) elseif not base.overrides.dat_s then local exceptions = "exceptions are nouns in -ir, -ó or -i; proper nouns; plural-only nouns; nouns with " .. "stem contraction or j-infix; nouns whose stem ends in two or more consonants, except for -kk and " .. "-pp; and nouns whose stem ends in a vowel or vowel + r" if base.definiteness == "indef" then error(("Saw masculine stem '%s' and no dative override: Most indefinite-only masculine nouns must " .. "explicitly specify the indefinite ending of the dative singular using an override of the form " .. "'datINDEF'; %s"):format(stem, exceptions)) else error(("Saw masculine stem '%s' and no dative override: Most masculine nouns must explicitly specify " .. "the indefinite and definite endings of the dative singular using an override of the form " .. "'datINDEF/DEF'; %s"):format(stem, exceptions)) end end props.default_dat_sg = default_dat_sg end -- Determine the stems and other properties to use for each property set. The list of such properties is given in the -- comment above create_base(), along with the explanation of what a property set is and why we have multiple such -- property sets (generally, one per combination of control specs such as 'con,-con' and 'umut,uUmut'). There are -- currently 9 singular stems and a corresponding 9 plural stems. local function determine_props(base) -- Now determine all the props for each prop set. for _, props in ipairs(base.prop_sets) do -- Determine the default dative singular for masculine nouns using declension "m". determine_default_masc_dat_sg(base, props) -- Almost all nouns have dative plural -um, which triggers u-mutation, so we need to compute the u-mutation -- stem using "umut" if not specifically given. Set `defaulted` so an error isn't triggered if there's no -- special u-mutated form. local props_umut = props.umut if not props_umut and (not props.unumut or props.unumut.form:find("^%-")) then props_umut = { form = "umut", defaulted = true } end -- First do all the stems, handling overall and plural-specific stems separately. for _, prefix in ipairs { "", "pl_" } do local base_stem, base_vstem if prefix == "" then base_stem = base.stem base_vstem = base.vstem else base_stem = base.plstem base_vstem = base.plvstem end -- The plstem is almost never set, so don't do a lot of unnecessary computation. if prefix == "pl_" and not base_stem then break end local stem, nonvstem, umut_nonvstem, imut_nonvstem, vstem, umut_vstem, imut_vstem, null_defvstem, umut_null_defvstem if props.unumut and not props.unumut.form:find("^%-") then umut_nonvstem = base_stem nonvstem = apply_reverse_u_mutation(umut_nonvstem, props.unumut.form, not props.unumut.defaulted) stem = nonvstem if base.need_imut then imut_nonvstem = apply_i_mutation(nonvstem, base.imutval) end if base_vstem then error(("Don't currently know how to combine '%svstem:' with 'unumut' specs"):format( prefix == "pl_" and "pl" or "")) end if props.con and props.con.form == "con" then umut_vstem = com.apply_contraction(base_stem) else umut_vstem = base_stem end vstem = apply_reverse_u_mutation(umut_vstem, props.unumut.form, not props.unumut.defaulted) if base.need_imut then imut_vstem = apply_i_mutation(vstem, base.imutval) end local props_unumut_form = props.unumut.form if props.defcon and props.defcon.form == "defcon" then umut_null_defvstem = com.apply_contraction(base_stem) else umut_null_defvstem = base_stem end null_defvstem = apply_reverse_u_mutation(umut_null_defvstem, props_unumut_form, not props.unumut.defaulted) elseif props.unimut and not props.unimut.form:find("^%-") then imut_nonvstem = base_stem local has_contraction = props.con and props.con.form == "con" nonvstem = apply_reverse_i_mutation(imut_nonvstem, base.unimutval, not has_contraction) stem = nonvstem if props_umut then umut_nonvstem = apply_u_mutation(nonvstem, props_umut.form, not props_umut.defaulted) end if base_vstem then error(("Don't currently know how to combine '%svstem:' with 'unimut' specs"):format( prefix == "pl_" and "pl" or "")) end if has_contraction then imut_vstem = com.apply_contraction(base_stem) else imut_vstem = base_stem end vstem = apply_reverse_i_mutation(imut_vstem, base.unimutval, "error if unmatchable") if props_umut then umut_vstem = apply_u_mutation(vstem, props_umut.form, not props_umut.defaulted) end if props.defcon and props.defcon.form == "defcon" then error("Don't currently know how to combine 'defcon' with 'unimut' specs") end base.need_imut = true elseif props_umut then stem = base_stem nonvstem = stem umut_nonvstem = apply_u_mutation(nonvstem, props_umut.form, not props_umut.defaulted) if base.need_imut then imut_nonvstem = apply_i_mutation(nonvstem, base.imutval) end vstem = base_vstem or base_stem if props.con and props.con.form == "con" then vstem = com.apply_contraction(vstem) end umut_vstem = apply_u_mutation(vstem, props_umut.form, not props_umut.defaulted) if base.need_imut then imut_vstem = apply_i_mutation(vstem, base.imutval) end if props.defcon and props.defcon.form == "defcon" then null_defvstem = com.apply_contraction(base_stem) else null_defvstem = base_stem end umut_null_defvstem = apply_u_mutation(null_defvstem, props_umut.form, not props_umut.defaulted) else -- Normally u-mutated forms should always be available, unless 'unumut' is in effect. error(("Internal error: Neither 'unumut' or 'umut' specified: %s"):format(dump(props))) end props[prefix .. "stem"] = stem if nonvstem ~= stem then props[prefix .. "nonvstem"] = nonvstem end if umut_nonvstem ~= nonvstem then -- For 'con' and 'defcon' below, footnotes can be placed on -con or -defcon so we have to check for -- those footnotes as well as checking for the vstem and such being different, so the -con and -defcon -- footnotes are still active. However, there's no such thing as -umut, and any time that there's an -- explicit umut variant given, umut_nonvstem will be different from nonvstem (otherwise an error will -- occur in apply_u_mutation), so we don't need this extra check here. if props_umut then umut_nonvstem = iut.combine_form_and_footnotes(umut_nonvstem, props_umut.footnotes) end props[prefix .. "umut_nonvstem"] = umut_nonvstem end if base.need_imut then -- imut footnotes handled specially below props[prefix .. "imut_nonvstem"] = imut_nonvstem end if vstem ~= stem or props.con and props.con.footnotes then -- See comment above for why we need to check for props.con.footnotes (basically, to handle footnotes on -- -con). if props.con then vstem = iut.combine_form_and_footnotes(vstem, props.con.footnotes) end props[prefix .. "vstem"] = vstem end if umut_vstem ~= vstem or props.con and props.con.footnotes then -- See comment above under `umut_nonvstem ~= nonvstem`. There's no -umut so whenever there's a specific -- umut variant with footnote, umut_vstem will be different from vstem so we don't need to check for -- `or props_umut and props_umut.footnotes` above. local footnotes = iut.combine_footnotes(props.con and props.con.footnotes or nil, props_umut and props_umut.footnotes or nil) umut_vstem = iut.combine_form_and_footnotes(umut_vstem, footnotes) props[prefix .. "umut_vstem"] = umut_vstem end if base.need_imut then -- imut footnotes handled specially below props[prefix .. "imut_vstem"] = imut_vstem end if null_defvstem ~= nonvstem or props.defcon and props.defcon.footnotes then -- See comment above for why we need to check for props.defcon.footnotes (basically, to handle footnotes -- on -defcon). if props.defcon then null_defvstem = iut.combine_form_and_footnotes(null_defvstem, props.defcon.footnotes) end props[prefix .. "null_defvstem"] = null_defvstem end if umut_null_defvstem ~= null_defvstem or props.defcon and props.defcon.footnotes then -- Analogous situation to the clause above that checks for `umut_vstem ~= vstem`. local footnotes = iut.combine_footnotes(props.defcon and props.defcon.footnotes or nil, props_umut and props_umut.footnotes or nil) umut_null_defvstem = iut.combine_form_and_footnotes(umut_null_defvstem, footnotes) props[prefix .. "umut_null_defvstem"] = umut_null_defvstem end end -- Do the j-infix, v-infix, imut, unimut and unumut properties. if props.j then props.jinfix = props.j.form == "j" and "j" or "" props.jinfix_footnotes = props.j.footnotes props.j = nil end if props.v then props.vinfix = props.v.form == "v" and "v" or "" props.vinfix_footnotes = props.v.footnotes props.v = nil end if props.imut then props.imut_footnotes = props.imut.footnotes props.imut = props.imut.form == "imut" and true or false end if props.unimut then props.unimut_footnotes = props.unimut.footnotes props.unimut = props.unimut.form == "unimut" and true or false end if props.unumut then props.unumut_footnotes = props.unumut.footnotes props.unumut = props.unumut.form end end end local function detect_indicator_spec(base) base.prop_sets = { {} } if base.adjspec then process_declnumber(base) synthesize_adj_lemma(base) elseif base.props.builtin then determine_builtin_props(base) else -- Replace # and ## in all overridable stems as well as all overrides. for _, stemkey in ipairs(overridable_stems) do base[stemkey] = com.replace_hashvals(base[stemkey], base.lemma) end map_all_overrides(base, function(formobj) formobj.form = com.replace_hashvals(formobj.form, base.lemma) end) expand_property_sets(base) if base.definiteness == "def" then synthesize_indefinite_lemma(base) end if base.number == "pl" then synthesize_singular_lemma(base) end determine_declension(base) determine_props(base) end end local function detect_all_indicator_specs(alternant_multiword_spec) -- Keep track of all genders seen in the singular and plural so we can determine whether to add the term to -- [[:Category:Icelandic nouns that change gender in the plural]]. FIXME: Is this needed for Icelandic? It's copied -- from Czech. alternant_multiword_spec.sg_genders = {} alternant_multiword_spec.pl_genders = {} iut.map_word_specs(alternant_multiword_spec, function(base) detect_indicator_spec(base) if base.number ~= "pl" then alternant_multiword_spec.sg_genders[base.actual_gender] = true end if base.number ~= "sg" then alternant_multiword_spec.pl_genders[base.actual_gender] = true end end) end local propagate_multiword_properties local function propagate_alternant_properties(alternant_spec, property, mixed_value, nouns_only) local seen_property for _, multiword_spec in ipairs(alternant_spec.alternants) do propagate_multiword_properties(multiword_spec, property, mixed_value, nouns_only) if seen_property == nil then seen_property = multiword_spec[property] elseif multiword_spec[property] and seen_property ~= multiword_spec[property] then seen_property = mixed_value end end alternant_spec[property] = seen_property end propagate_multiword_properties = function(multiword_spec, property, mixed_value, nouns_only) local seen_property = nil local last_seen_nounal_pos = 0 local word_specs = multiword_spec.alternant_or_word_specs or multiword_spec.word_specs for i = 1, #word_specs do local is_nounal if word_specs[i].alternants then propagate_alternant_properties(word_specs[i], property, mixed_value) is_nounal = not not word_specs[i][property] elseif nouns_only then is_nounal = is_regular_noun(word_specs[i]) else is_nounal = not not word_specs[i][property] end if is_nounal then if not word_specs[i][property] then error("Internal error: noun-type word spec without " .. property .. " set") end for j = last_seen_nounal_pos + 1, i - 1 do word_specs[j][property] = word_specs[j][property] or word_specs[i][property] end last_seen_nounal_pos = i if seen_property == nil then seen_property = word_specs[i][property] elseif seen_property ~= word_specs[i][property] then seen_property = mixed_value end end end if last_seen_nounal_pos > 0 then for i = last_seen_nounal_pos + 1, #word_specs do word_specs[i][property] = word_specs[i][property] or word_specs[last_seen_nounal_pos][property] end end multiword_spec[property] = seen_property end local function propagate_properties_downward(alternant_multiword_spec, property, default_propval) local function set_and_fetch(obj, default) local retval if obj[property] then retval = obj[property] else obj[property] = default retval = default end if not obj["actual_" .. property] then obj["actual_" .. property] = retval end return retval end local propval1 = set_and_fetch(alternant_multiword_spec, default_propval) for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do local propval2 = set_and_fetch(alternant_or_word_spec, propval1) if alternant_or_word_spec.alternants then for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do local propval3 = set_and_fetch(multiword_spec, propval2) for _, word_spec in ipairs(multiword_spec.word_specs) do local propval4 = set_and_fetch(word_spec, propval3) if propval4 == "mixed" then -- FIXME, use clearer error message. error("Attempt to assign mixed " .. property .. " to word") end set_and_fetch(word_spec, propval4) end end else if propval2 == "mixed" then -- FIXME, use clearer error message. error("Attempt to assign mixed " .. property .. " to word") end set_and_fetch(alternant_or_word_spec, propval2) end end end --[=[ Propagate `property` (one of "gender", "number" or "definiteness") from nouns to adjacent adjectives. We proceed as follows: 1. We assume the properties in question are already set on all nouns. This should happen in set_defaults_and_check_bad_indicators(). 2. We first propagate properties upwards and sideways. We recurse downwards from the top. When we encounter a multiword spec, we proceed left to right looking for a noun. When we find a noun, we fetch its property (recursing if the noun is an alternant), and propagate it to any adjectives to its left, up to the next noun to the left. When we have processed the last noun, we also propagate its property value to any adjectives to the right (to handle e.g. [[svefninn langi]] "the long sleep", where the adjective [[langi]] should inherit the 'masculine', 'singular' and 'definite' properties of [[svefninn]]). Finally, we set the property value for the multiword spec itself by combining all the non-nil properties of the individual elements. If all non-nil properties have the same value, the result is that value, otherwise it is `mixed_value` (which is "mixed" for gender, but "both" for number and "bothdef" for definiteness). 3. When we encounter an alternant spec in this process, we recursively process each alternant (which is a multiword spec) using the previous step, and combine any non-nil properties we encounter the same way as for multiword specs. 4. The effect of steps 2 and 3 is to set the property of each alternant and multiword spec based on its children or its neighbors. ]=] local function propagate_properties(alternant_multiword_spec, property, default_propval, mixed_value) propagate_multiword_properties(alternant_multiword_spec, property, mixed_value, "nouns only") propagate_multiword_properties(alternant_multiword_spec, property, mixed_value, false) propagate_properties_downward(alternant_multiword_spec, property, default_propval) end local function determine_noun_status(alternant_multiword_spec) for i, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do if alternant_or_word_spec.alternants then local is_noun = false for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do for j, word_spec in ipairs(multiword_spec.word_specs) do if is_regular_noun(word_spec) then multiword_spec.first_noun = j is_noun = true break end end end if is_noun then alternant_multiword_spec.first_noun = i end elseif is_regular_noun(alternant_or_word_spec) then alternant_multiword_spec.first_noun = i return end end end -- Set the part of speech based on properties of the individual words. local function set_pos(alternant_multiword_spec) if not alternant_multiword_spec.pos then if alternant_multiword_spec.saw_builtin and not alternant_multiword_spec.saw_non_builtin then alternant_multiword_spec.pos = "pronoun" else alternant_multiword_spec.pos = "noun" end end end local function normalize_all_lemmas(alternant_multiword_spec) iut.map_word_specs(alternant_multiword_spec, function(base) local lemma = base.orig_lemma_no_links base.actual_lemma = lemma base.lemma = base.decllemma or lemma base.source_template = alternant_multiword_spec.source_template end) end local function decline_noun(base) for _, props in ipairs(base.prop_sets) do if not decls[base.decl] then error("Internal error: Unrecognized declension type '" .. base.decl .. "'") end decls[base.decl](base, props) end handle_derived_slots_and_overrides(base) local function copy(from_slot, to_slot) base.forms["ind_" .. to_slot] = base.forms["ind_" .. from_slot] base.forms["def_" .. to_slot] = base.forms["def_" .. from_slot] end if base.actual_number ~= base.number then local source_num = base.number == "sg" and "_s" or "_p" local dest_num = base.number == "sg" and "_p" or "_s" for _, case in ipairs(cases) do copy(case .. source_num, case .. dest_num) copy("nom" .. source_num .. "_linked", "nom" .. dest_num .. "_linked") end if base.actual_number ~= "both" then local erase_num = base.actual_number == "sg" and "_p" or "_s" for _, case in ipairs(cases) do base.forms["ind_" .. case .. erase_num] = nil base.forms["def_" .. case .. erase_num] = nil end base.forms["ind_nom" .. erase_num .. "_linked"] = nil base.forms["def_nom" .. erase_num .. "_linked"] = nil end end process_addnote_specs(base) end -- Compute the categories to add the noun to, as well as the annotation to display in the -- declension title bar. We combine the code to do these functions as both categories and -- title bar contain similar information. local function compute_categories_and_annotation(alternant_multiword_spec) local all_cats = {} local plpos = require(en_utilities_module).pluralize(alternant_multiword_spec.pos) local function inscat(cattype) m_table.insertIfNot(all_cats, "Icelandic " .. cattype) end local function inscat_noun(cattype) if plpos == "nouns" then inscat(cattype) end end if alternant_multiword_spec.saw_indecl and not alternant_multiword_spec.saw_non_indecl then inscat("indeclinable " .. plpos) end if alternant_multiword_spec.saw_unknown_decl and not alternant_multiword_spec.saw_non_unknown_decl then inscat(plpos .. " with unknown declension") end if alternant_multiword_spec.actual_number == "sg" then inscat_noun("uncountable nouns") elseif alternant_multiword_spec.actual_number == "pl" then inscat_noun("pluralia tantum") end local annparts = {} local irregs = {} local genderspecs = {} local stemspecs = {} local scrape_chains = {} local function insann(txt, joiner) if joiner and annparts[1] then table.insert(annparts, joiner) end table.insert(annparts, txt) end local function do_word_spec(base) local actual_gender = gender_code_to_desc[base.actual_gender] local declined_gender = gender_code_to_desc[base.gender] local gender if actual_gender ~= declined_gender then gender = ("%s (declined as %s)"):format(actual_gender, declined_gender) inscat_noun("nouns with actual gender different from declined gender") else gender = actual_gender end if gender then m_table.insertIfNot(genderspecs, gender) end for _, props in ipairs(base.prop_sets) do -- User-specified 'decllemma:' indicates irregular stem. if base.decllemma then m_table.insertIfNot(irregs, "irreg-stem") inscat_noun("nouns with irregular stem") end m_table.insertIfNot(stemspecs, props.stem) end end local key_entry = alternant_multiword_spec.first_noun or 1 if #alternant_multiword_spec.alternant_or_word_specs >= key_entry then local alternant_or_word_spec = alternant_multiword_spec.alternant_or_word_specs[key_entry] if alternant_or_word_spec.alternants then for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do key_entry = multiword_spec.first_noun or 1 if #multiword_spec.word_specs >= key_entry then do_word_spec(multiword_spec.word_specs[key_entry]) end end else do_word_spec(alternant_or_word_spec) end end iut.map_word_specs(alternant_multiword_spec, function(base) if base.scrape_chain[1] then local linked_scrape_chain = {} for _, element in ipairs(base.scrape_chain) do table.insert(linked_scrape_chain, ("[[%s]]"):format(element)) end m_table.insertIfNot(scrape_chains, table.concat(linked_scrape_chain, " -> ")) end end) if alternant_multiword_spec.actual_number == "sg" or alternant_multiword_spec.actual_number == "pl" then -- not "both" or "none" (for [[sebe]]) insann(alternant_multiword_spec.actual_number .. "-only", " ") end if #genderspecs > 0 then insann(table.concat(genderspecs, " // "), " ") end if #irregs > 0 then insann(table.concat(irregs, " // "), " ") end if #scrape_chains > 0 then insann(("based on %s"):format(m_table.serialCommaJoin(scrape_chains)), ", ") inscat(plpos .. " declined using scraped base declensions") end alternant_multiword_spec.annotation = table.concat(annparts) if #stemspecs > 1 then inscat_noun("nouns with multiple stems") end if alternant_multiword_spec.actual_number == "both" and not m_table.deepEquals(alternant_multiword_spec.sg_genders, alternant_multiword_spec.pl_genders) then inscat_noun("nouns that change gender in the plural") end alternant_multiword_spec.categories = all_cats end local function show_forms(alternant_multiword_spec) local lemmas = {} local max_num_words = 1 for _, slot in ipairs(potential_lemma_slots) do if alternant_multiword_spec.forms[slot] then for _, formobj in ipairs(alternant_multiword_spec.forms[slot]) do table.insert(lemmas, formobj) local no_affix_form = formobj.form:gsub("^%-", ""):gsub("%-$", "") local num_words = #(rsplit(no_affix_form, "[ -]+")) max_num_words = math.max(max_num_words, num_words) end break end end alternant_multiword_spec.max_num_words = max_num_words local props = { lemmas = lemmas, slot_list = alternant_multiword_spec.noun_slots, lang = lang, } iut.show_forms(alternant_multiword_spec.forms, props) end local function make_table(alternant_multiword_spec) local forms = alternant_multiword_spec.forms local frame = mw.getCurrentFrame() local function template_prelude() return m_inflection_table.make_top{ title = '{title}{annotation}', palette = 'blue', tall = 'yes', } end local function template_postlude() return m_inflection_table.make_bottom{ notes = '{footnote}', } end local table_spec_both = template_prelude() .. [=[ ! rowspan="2" | ! colspan="2" | singular ! colspan="2" | plural |- ! class="secondary" | indefinite ! class="secondary" | definite ! class="secondary" | indefinite ! class="secondary" | definite |- ! nominative | {ind_nom_s} | {def_nom_s} | {ind_nom_p} | {def_nom_p} |- ! accusative | {ind_acc_s} | {def_acc_s} | {ind_acc_p} | {def_acc_p} |- ! dative | {ind_dat_s} | {def_dat_s} | {ind_dat_p} | {def_dat_p} |- ! genitive | {ind_gen_s} | {def_gen_s} | {ind_gen_p} | {def_gen_p} ]=] .. template_postlude() local function get_table_spec_one_number(number, numcode) local table_spec_one_number = [=[ ! rowspan="2" | ! colspan="2" | NUMBER |- ! class="secondary" | indefinite ! class="secondary" | definite |- ! nominative | {ind_nom_NUM} | {def_nom_NUM} |- ! accusative | {ind_acc_NUM} | {def_acc_NUM} |- ! dative | {ind_dat_NUM} | {def_dat_NUM} |- ! genitive | {ind_gen_NUM} | {def_gen_NUM} ]=] return template_prelude() .. table_spec_one_number:gsub("NUMBER", number):gsub("NUM", numcode) .. template_postlude() end local function get_table_spec_one_number_one_def(number, numcode, definiteness, defcode) local table_spec_one_number_one_def = [=[ ! colspan="2" | DEFINITENESS NUMBER |- ! nominative | {DEF_nom_NUM} |- ! accusative | {DEF_acc_NUM} |- ! dative | {DEF_dat_NUM} |- ! genitive | {DEF_gen_NUM} ]=] return template_prelude() .. (table_spec_one_number_one_def:gsub("NUMBER", number):gsub("NUM", numcode) :gsub("DEFINITENESS", definiteness):gsub("DEF", defcode)) .. template_postlude() end if alternant_multiword_spec.title then forms.title = alternant_multiword_spec.title else forms.title = 'Declension of <i lang="is">' .. forms.lemma .. '</i>' end local annotation = alternant_multiword_spec.annotation if annotation == "" then forms.annotation = "" else forms.annotation = " (<span style=\"font-size: smaller;\">" .. annotation .. "</span>)" end local number, numcode if alternant_multiword_spec.actual_number == "sg" then number, numcode = "singular", "s" elseif alternant_multiword_spec.actual_number == "pl" then number, numcode = "plural", "p" elseif alternant_multiword_spec.actual_number == "none" then -- used for [[sebe]] -- FIXME: Update for Icelandic number, numcode = "", "s" end local definiteness, defcode if alternant_multiword_spec.definiteness == "indef" then definiteness, defcode = "indefinite", "ind" elseif alternant_multiword_spec.definiteness == "def" then definiteness, defcode = "definite", "def" elseif alternant_multiword_spec.definiteness == "none" then definiteness, defcode = "", "ind" end local table_spec = alternant_multiword_spec.actual_number ~= "both" and alternant_multiword_spec.definiteness ~= "bothdef" and get_table_spec_one_number_one_def(number, numcode, definiteness, defcode) or alternant_multiword_spec.actual_number == "both" and table_spec_both or get_table_spec_one_number(number, numcode) return m_string_utilities.format(table_spec, forms) end local function compute_headword_genders(alternant_multiword_spec) local genders = {} local number if alternant_multiword_spec.actual_number == "pl" then number = "-p" else number = "" end iut.map_word_specs(alternant_multiword_spec, function(base) if base.actual_gender ~= "none" then m_table.insertIfNot(genders, base.actual_gender .. number) end end) return genders end -- Externally callable function to parse and decline a noun given user-specified arguments and the argument spec -- `argspec` (specified because the user may give multiple such specs). Return value is ALTERNANT_MULTIWORD_SPEC, an -- object where the declined forms are in `ALTERNANT_MULTIWORD_SPEC.forms` for each slot. If there are no values for a -- slot, the slot key will be missing. The value for a given slot is a list of objects {form=FORM, footnotes=FOOTNOTES}. function export.do_generate_forms(args, argspec, source_template) local pagename = args.pagename or mw.loadData("Module:headword/data").pagename local parse_props = { parse_indicator_spec = function(angle_bracket_spec, lemma) return parse_indicator_spec(angle_bracket_spec, lemma, pagename) end, angle_brackets_omittable = true, allow_blank_lemma = true, } local alternant_multiword_spec = iut.parse_inflected_text(argspec, parse_props) alternant_multiword_spec.title = args.title alternant_multiword_spec.pos = args.pos alternant_multiword_spec.args = args alternant_multiword_spec.source_template = source_template local scrape_errors = {} iut.map_word_specs(alternant_multiword_spec, function(base) if base.scrape_error then table.insert(scrape_errors, base.scrape_error) end end) if scrape_errors[1] then alternant_multiword_spec.scrape_errors = scrape_errors else normalize_all_lemmas(alternant_multiword_spec) set_all_defaults_and_check_bad_indicators(alternant_multiword_spec) -- These need to happen before detect_all_indicator_specs() so that adjectives get their genders and number -- set appropriately, which are needed to correctly synthesize the adjective lemma. propagate_properties(alternant_multiword_spec, "number", "both", "both") -- FIXME, the default value (third param) used to be 'm' with a comment indicating that this applied only to -- plural adjectives, where it didn't matter; but in Icelandic, plural adjectives are distinguished for gender. -- Make sure 'mixed' works. propagate_properties(alternant_multiword_spec, "gender", "mixed", "mixed") propagate_properties(alternant_multiword_spec, "definiteness", "bothdef", "bothdef") detect_all_indicator_specs(alternant_multiword_spec) -- Propagate 'actual_number' after calling detect_all_indicator_specs(), which sets 'actual_number' for -- adjectives. propagate_properties(alternant_multiword_spec, "actual_number", "both", "both") determine_noun_status(alternant_multiword_spec) set_pos(alternant_multiword_spec) alternant_multiword_spec.noun_slots = get_noun_slots(alternant_multiword_spec) local inflect_props = { skip_slot = function(slot) return skip_slot(alternant_multiword_spec.actual_number, alternant_multiword_spec.definiteness, slot) end, slot_list = alternant_multiword_spec.noun_slots, inflect_word_spec = decline_noun, } iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props) compute_categories_and_annotation(alternant_multiword_spec) alternant_multiword_spec.genders = compute_headword_genders(alternant_multiword_spec) end if args.json then alternant_multiword_spec.args = nil return require("Module:JSON").toJSON(alternant_multiword_spec) end return alternant_multiword_spec end -- Entry point for {{is-ndecl}}. Template-callable function to parse and decline a noun given -- user-specified arguments and generate a displayable table of the declined forms. function export.show(frame) local parent_args = frame:getParent().args local params = { [1] = { required = true, list = true, default = "akur<m.#>" }, deriv = { list = true }, id = {}, pos = {}, title = {}, pagename = {}, json = { type = "boolean" }, } local args = m_para.process(parent_args, params) local alternant_multiword_specs = {} for i, argspec in ipairs(args[1]) do alternant_multiword_specs[i] = export.do_generate_forms(args, argspec, "is-ndecl") end if args.json then -- JSON return value if #args[1] == 1 then return alternant_multiword_specs[1] else return alternant_multiword_specs end end local parts = {} local function ins(txt) table.insert(parts, txt) end for _, alternant_multiword_spec in ipairs(alternant_multiword_specs) do if not alternant_multiword_spec.scrape_errors then show_forms(alternant_multiword_spec) end if alternant_multiword_spec.header then ins(("'''%s:'''\n"):format(alternant_multiword_spec.header)) end if alternant_multiword_spec.q then ins(("''%s''\n"):format(alternant_multiword_spec.q)) end local categories if alternant_multiword_spec.scrape_errors then local errmsgs = {} for _, scrape_error in ipairs(alternant_multiword_spec.scrape_errors) do table.insert(errmsgs, '<span style="font-weight: bold; color: var(--wikt-palette-red,#CC2200);">' .. scrape_error .. "</span>") end -- Surround the messages with a <div> because the table normally does that, and we want to ensure -- similar formatting with respect to newlines. ins("<div>" .. table.concat(errmsgs, "<br />") .. "</div>") categories = { "Icelandic scraping errors in Template:is-ndecl" } else ins(make_table(alternant_multiword_spec)) categories = alternant_multiword_spec.categories end ins(require("Module:utilities").format_categories(categories, lang, nil, nil, force_cat)) end return table.concat(parts) end return export rtbmoasgcvs3biu2mtsdinmh7cg3sqy Module:tungumál 828 119299 393946 2026-10-08T19:30:14Z Ow! That Hurts! 8952 skapað 393946 Scribunto text/plain --[==[ intro: This module implements fetching of language-specific information and processing text in a given language. ===Types of languages=== There are two types of languages: full languages and etymology-only languages. The essential difference is that only full languages appear in L2 headings in vocabulary entries, and hence categories like [[:Category:French nouns]] exist only for full languages. Etymology-only languages have either a full language or another etymology-only language as their parent (in the parent-child inheritance sense), and for etymology-only languages with another etymology-only language as their parent, a full language can always be derived by following the parent links upwards. For example, "Canadian French", code `fr-CA`, is an etymology-only language whose parent is the full language "French", code `fr`. An example of an etymology-only language with another etymology-only parent is "Northumbrian Old English", code `ang-nor`, which has "Anglian Old English", code `ang-ang` as its parent; this is an etymology-only language whose parent is "Old English", code `ang`, which is a full language. (This is because Northumbrian Old English is considered a variety of Anglian Old English.) Sometimes the parent is the "Undetermined" language, code `und`; this is the case, for example, for "substrate" languages such as "Pre-Greek", code `qsb-grc`, and "the BMAC substrate", code `qsb-bma`. It is important to distinguish language ''parents'' from language ''ancestors''. The parent-child relationship is one of containment, i.e. if X is a child of Y, X is considered a variety of Y. On the other hand, the ancestor-descendant relationship is one of descent in time. For example, "Classical Latin", code `la-cla`, and "Late Latin", code `la-lat`, are both etymology-only languages with "Latin", code `la`, as their parents, because both of the former are varieties of Latin. However, Late Latin does *NOT* have Classical Latin as its parent because Late Latin is *not* a variety of Classical Latin; rather, it is a descendant. There is in fact a separate `ancestors` field that is used to express the ancestor-descendant relationship, and Late Latin's ancestor is given as Classical Latin. It is also important to note that sometimes an etymology-only language is actually the conceptual ancestor of its parent language. This happens, for example, with "Old Italian" (code `roa-oit`), which is an etymology-only variant of full language "Italian" (code `it`), and with "Old Latin" (code `itc-ola`), which is an etymology-only variant of Latin. In both cases, the full language has the etymology-only variant listed as an ancestor. This allows a Latin term to inherit from Old Latin using the {{tl|inh}} template (where in this template, "inheritance" refers to ancestral inheritance, i.e. inheritance in time, rather than in the parent-child sense); likewise for Italian and Old Italian. Full languages come in three subtypes: * {regular}: This indicates a full language that is attested according to [[WT:CFI]] and therefore permitted in the main namespace. There may also be reconstructed terms for the language, which are placed in the {Reconstruction} namespace and must be prefixed with * to indicate a reconstruction. Most full languages are natural (not constructed) languages, but a few constructed languages (e.g. Esperanto and Volapük, among others) are also allowed in the mainspace and considered regular languages. * {reconstructed}: This language is not attested according to [[WT:CFI]], and therefore is allowed only in the {Reconstruction} namespace. All terms in this language are reconstructed, and must be prefixed with *. Languages such as Proto-Indo-European and Proto-Germanic are in this category. * {appendix-constructed}: This language is attested but does not meet the additional requirements set out for constructed languages ([[WT:CFI#Constructed languages]]). Its entries must therefore be in the Appendix namespace, but they are not reconstructed and therefore should not have * prefixed in links. Most constructed languages are of this subtype. Both full languages and etymology-only languages have a {Language} object associated with them, which is fetched using the {getByCode} function in [[Module:languages]] to convert a language code to a {Language} object. Depending on the options supplied to this function, etymology-only languages may or may not be accepted, and family codes may be accepted (returning a {Family} object as described in [[Module:families]]). There are also separate {getByCanonicalName} functions in [[Module:languages]] and [[Module:etymology languages]] to convert a language's canonical name to a {Language} object (depending on whether the canonical name refers to a full or etymology-only language). ===Textual representations=== Textual strings belonging to a given language come in several different ''text variants'': # The ''input text'' is what the user supplies in wikitext, in the parameters to {{tl|m}}, {{tl|l}}, {{tl|ux}}, {{tl|t}}, {{tl|lang}} and the like. # The ''corrected input text'' is the input text with some corrections and/or normalizations applied, such as bad-character replacements for certain languages, like replacing `l` or `1` to [[palochka]] in some languages written in Cyrillic. (FIXME: This currently goes under the name ''display text'' but that will be repurposed below. Also, [[User:Surjection]] suggests renaming this to ''normalized input text'', but "normalized" is used in a different sense in [[Module:usex]].) # The ''display text'' is the text in the form as it will be displayed to the user. This is what appears in headwords, in usexes, in displayed internal links, etc. This can include accent marks that are removed to form the stripped display text (see below), as well as embedded bracketed links that are variously processed further. The display text is generated from the corrected input text by applying language-specific transformations; for most languages, there will be no such transformations. The general reason for having a difference between input and display text is to allow for extra information in the input text that is not displayed to the user but is sent to the transliteration module. Note that having different display and input text is only supported currently through special-casing but will be generalized. Examples of transformations are: (1) Removing the {{cd|^}} that is used in certain East Asian (and possibly other unicameral) languages to indicate capitalization of the transliteration (which is currently special-cased); (2) for Korean, removing or otherwise processing hyphens (which is currently special-cased); (3) for Arabic, removing a ''sukūn'' diacritic placed over a ''tāʔ marbūṭa'' (like this: ةْ) to indicate that the ''tāʔ marbūṭa'' is pronounced and transliterated as /t/ instead of being silent [NOTE, NOT IMPLEMENTED YET]; (4) for Thai and Khmer, converting space-separated words to bracketed words and resolving respelling substitutions such as `[กรีน/กฺรีน]`, which indicate how to transliterate given words [NOTE, NOT IMPLEMENTED YET except in language-specific templates like {{tl|th-usex}}]. ## The ''right-resolved display text'' is the result of removing brackets around one-part embedded links and resolving two-part embedded links into their right-hand components (i.e. converting two-part links into the displayed form). The process of right-resolution is what happens when you call {{cd|remove_links()}} in [[Module:links]] on some text. When applied to the display text, it produces exactly what the user sees, without any link markup. # The ''stripped display text'' is the result of applying diacritic-stripping to the display text. ## The ''left-resolved stripped display text'' [NEED BETTER NAME] is the result of applying left-resolution to the stripped display text, i.e. similar to right-resolution but resolving two-part embedded links into their left-hand components (i.e. the linked-to page). If the display text refers to a single page, the resulting of applying diacritic stripping and left-resolution produces the ''logical pagename''. # The ''physical pagename text'' is the result of converting the stripped display text into physical page links. If the stripped display text contains embedded links, the left side of those links is converted into physical page links; otherwise, the entire text is considered a pagename and converted in the same fashion. The conversion does three things: (1) converts characters not allowed in pagenames into their "unsupported title" representation, e.g. {{cd|Unsupported titles/`gt`}} in place of the logical name {{cd|>}}; (2) handles certain special-cased unsupported-title logical pagenames, such as {{cd|Unsupported titles/Space}} in place of {{cd|[space]}} and {{cd|Unsupported titles/Ancient Greek dish}} in place of a very long Greek name for a gourmet dish as found in Aristophanes; (3) converts "mammoth" pagenames such as [[a]] into their appropriate split component, e.g. [[a/languages A to L]]. # The ''source translit text'' is the text as supplied to the language-specific {{cd|transliterate()}} method. The form of the source translit text may need to be language-specific, e.g Thai and Khmer will need the corrected input text, whereas other languages may need to work off the display text. [FIXME: It's still unclear to me how embedded bracketed links are handled in the existing code.] In general, embedded links need to be right-resolved (see above), but when this happens is unclear to me [FIXME]. Some languages have a chop-up-and-paste-together scheme that sends parts of the text through the transliterate mechanism, and for others (those listed with "cont" in {{cd|substitution}} in [[Module:languages/data]]) they receive the full input text, but preprocessed in certain ways. (The wisdom of this is still unclear to me.) # The ''transliterated text'' (or ''transliteration'') is the result of transliterating the source translit text. Unlike for all the other text variants except the transcribed text, it is always in the Latin script. # The ''transcribed text'' (or ''transcription'') is the result of transcribing the source translit text, where "transcription" here means a close approximation to the phonetic form of the language in languages (e.g. Akkadian, Sumerian, Ancient Egyptian, maybe Tibetan) that have a wide difference between the written letters and spoken form. Unlike for all the other text variants other than the transliterated text, it is always in the Latin script. Currently, the transcribed text is always supplied manually be the user; there is no such thing as a {{cd|transcribe()}} method on language objects. # The ''sort key'' is the text used in sort keys for determining the placing of pages in categories they belong to. The sort key is generated from the pagename or a specified ''sort base'' by lowercasing, doing language-specific transformations and then uppercasing the result. If the sort base is supplied and is generated from input text, it needs to be converted to display text, have embedded links removed through right-resolution and have diacritic-stripping applied. # There are other text variants that occur in usexes (specifically, there are normalized variants of several of the above text variants), but we can skip them for now. The following methods exist on {Language} objects to convert between different text variants: # {correctInputText} (currently called {makeDisplayText}): This converts input text to corrected input text. # {stripDiacritics}: This converts to stripped display text. [FIXME: This needs some rethinking. In particular, {stripDiacritics} is sometimes called on input text, corrected input text or display text (in various paths inside of [[Module:links]], and, in the case of input text, usually from other modules). We need to make sure we don't try to convert input text to display text twice, but at the same time we need to support calling it directly on input text since so many modules do this. This means we need to add a parameter indicating whether the passed-in text is input, corrected input, or display text; if the former two, we call {correctInputText} ourselves.] # {logicalToPhysical}: This converts logical pagenames to physical pagenames. # {transliterate}: This appears to convert input text with embedded brackets removed into a transliteration. [FIXME: This needs some rethinking. In particular, it calls {processDisplayText} on its input, which won't work for Thai and Khmer, so we may need language-specific flags indicating whether to pass the input text directly to the language transliterate method. In addition, I'm not sure how embedded links are handled in the existing translit code; a lot of callers remove the links themselves before calling {transliterate()}, which I assume is wrong.] # {makeSortKey}: This converts display text (?) to a sort key. [FIXME: Clarify this.] ]==] local export = {} local debug_track_module = "Module:debug/track" local etymology_languages_data_module = "Module:etymology languages/data" local families_module = "Module:families" local headword_page_module = "Module:headword/page" local json_module = "Module:JSON" local language_like_module = "Module:language-like" local languages_data_module = "Module:languages/data" local languages_data_patterns_module = "Module:languages/data/patterns" local links_data_module = "Module:links/data" local load_module = "Module:load" local scripts_module = "Module:scripts" local scripts_data_module = "Module:scripts/data" local string_encode_entities_module = "Module:string/encode entities" local string_pattern_escape_module = "Module:string/patternEscape" local string_replacement_escape_module = "Module:string/replacementEscape" local string_utilities_module = "Module:string utilities" local table_module = "Module:table" local utilities_module = "Module:utilities" local wikimedia_languages_module = "Module:wikimedia languages" local mw = mw local string = string local table = table local char = string.char local concat = table.concat local find = string.find local floor = math.floor local get_by_code -- Defined below. local get_data_module_name -- Defined below. local get_extra_data_module_name -- Defined below. local getmetatable = getmetatable local gmatch = string.gmatch local gsub = string.gsub local insert = table.insert local ipairs = ipairs local is_known_language_tag = mw.language.isKnownLanguageTag local make_object -- Defined below. local match = string.match local next = next local pairs = pairs local remove = table.remove local require = require local select = select local setmetatable = setmetatable local sub = string.sub local type = type local unstrip = mw.text.unstrip -- Loaded as needed by findBestScript. local Hans_chars local Hant_chars local function check_object(...) check_object = require(utilities_module).check_object return check_object(...) end local function debug_track(...) debug_track = require(debug_track_module) return debug_track(...) end local function decode_entities(...) decode_entities = require(string_utilities_module).decode_entities return decode_entities(...) end local function decode_uri(...) decode_uri = require(string_utilities_module).decode_uri return decode_uri(...) end local function deep_copy(...) deep_copy = require(table_module).deepCopy return deep_copy(...) end local function encode_entities(...) encode_entities = require(string_encode_entities_module) return encode_entities(...) end local function get_L2_sort_key(...) get_L2_sort_key = require(headword_page_module).get_L2_sort_key return get_L2_sort_key(...) end local function get_script(...) get_script = require(scripts_module).getByCode return get_script(...) end local function find_best_script_without_lang(...) find_best_script_without_lang = require(scripts_module).findBestScriptWithoutLang return find_best_script_without_lang(...) end local function get_family(...) get_family = require(families_module).getByCode return get_family(...) end local function get_plaintext(...) get_plaintext = require(utilities_module).get_plaintext return get_plaintext(...) end local function get_wikimedia_lang(...) get_wikimedia_lang = require(wikimedia_languages_module).getByCode return get_wikimedia_lang(...) end local function keys_to_list(...) keys_to_list = require(table_module).keysToList return keys_to_list(...) end local function list_to_set(...) list_to_set = require(table_module).listToSet return list_to_set(...) end local function load_data(...) load_data = require(load_module).load_data return load_data(...) end local function make_family_object(...) make_family_object = require(families_module).makeObject return make_family_object(...) end local function pattern_escape(...) pattern_escape = require(string_pattern_escape_module) return pattern_escape(...) end local function replacement_escape(...) replacement_escape = require(string_replacement_escape_module) return replacement_escape(...) end local function safe_require(...) safe_require = require(load_module).safe_require return safe_require(...) end local function shallow_copy(...) shallow_copy = require(table_module).shallowCopy return shallow_copy(...) end local function split(...) split = require(string_utilities_module).split return split(...) end local function to_json(...) to_json = require(json_module).toJSON return to_json(...) end local function u(...) u = require(string_utilities_module).char return u(...) end local function ugsub(...) ugsub = require(string_utilities_module).gsub return ugsub(...) end local function ulen(...) ulen = require(string_utilities_module).len return ulen(...) end local function ulower(...) ulower = require(string_utilities_module).lower return ulower(...) end local function umatch(...) umatch = require(string_utilities_module).match return umatch(...) end local function uupper(...) uupper = require(string_utilities_module).upper return uupper(...) end local function track(page) debug_track("languages/" .. page) return true end local function normalize_code(code) return load_data(languages_data_module).aliases[code] or code end local function check_inputs(self, check, default, ...) local n = select("#", ...) if n == 0 then return false end local ret = check(self, (...)) if ret ~= nil then return ret elseif n > 1 then local inputs = {...} for i = 2, n do ret = check(self, inputs[i]) if ret ~= nil then return ret end end end return default end local function make_link(self, target, display) local prefix, main if self:getFamilyCode() == "qfa-sub" then prefix, main = display:match("^(the )(.*)") if not prefix then prefix, main = display:match("^(a )(.*)") end end return (prefix or "") .. "[[" .. target .. "|" .. (main or display) .. "]]" end -- Convert risky characters to HTML entities, which minimizes interference once returned (e.g. for "sms:a", "<!-- -->" etc.). local function escape_risky_characters(text) -- Spacing characters in isolation generally need to be escaped in order to be properly processed by the MediaWiki -- software. if umatch(text, "^%s*$") then return encode_entities(text, text) end return encode_entities(text, "!#%&*+/:;<=>?@[\\]_{|}") end -- Temporarily convert various formatting characters to PUA to prevent them from being disrupted by the substitution process. local function doTempSubstitutions(text, subbedChars, keepCarets, noTrim) -- Clone so that we don't insert any extra patterns into the table in package.loaded. For some reason, using require -- seems to keep memory use down; probably because the table is always cloned. local patterns = shallow_copy(require(languages_data_patterns_module)) if keepCarets then insert(patterns, "((\\+)%^)") insert(patterns, "((%^))") end -- Ensure any whitespace at the beginning and end is temp substituted, to prevent it from being accidentally -- trimmed. We only want to trim any final spaces added during the substitution process (e.g. by a module), which -- means we only do this during the first round of temp substitutions. if not noTrim then insert(patterns, "^([\128-\191\244]*(%s+))") insert(patterns, "((%s+)[\128-\191\244]*)$") end -- Pre-substitution, of "[[" and "]]", which makes pattern matching more accurate. text = gsub(text, "%f[%[]%[%[", "\1"):gsub("%f[%]]%]%]", "\2") local i = #subbedChars for _, pattern in ipairs(patterns) do -- Patterns ending in \0 stand are for things like "[[" or "]]"), so the inserted PUA are treated as breaks -- between terms by modules that scrape info from pages. local term_divider pattern = gsub(pattern, "%z$", function(divider) term_divider = divider == "\0" return "" end) text = gsub(text, pattern, function(...) local m = {...} local m1New = m[1] for k = 2, #m do local n = i + k - 1 subbedChars[n] = m[k] local byte2 = floor(n / 4096) % 64 + (term_divider and 128 or 136) local byte3 = floor(n / 64) % 64 + 128 local byte4 = n % 64 + 128 m1New = gsub(m1New, pattern_escape(m[k]), "\244" .. char(byte2) .. char(byte3) .. char(byte4), 1) end i = i + #m - 1 return m1New end) end text = gsub(text, "\1", "%[%["):gsub("\2", "%]%]") return text, subbedChars end -- Reinsert any formatting that was temporarily substituted. local function undoTempSubstitutions(text, subbedChars) for i = 1, #subbedChars do local byte2 = floor(i / 4096) % 64 + 128 local byte3 = floor(i / 64) % 64 + 128 local byte4 = i % 64 + 128 text = gsub(text, "\244[" .. char(byte2) .. char(byte2+8) .. "]" .. char(byte3) .. char(byte4), replacement_escape(subbedChars[i])) end text = gsub(text, "\1", "%[%["):gsub("\2", "%]%]") return text end -- Check if the raw text is an unsupported title, and if so return that. Otherwise, remove HTML entities. We do the -- pre-conversion to avoid loading the unsupported title list unnecessarily. local function checkNoEntities(text) local textNoEnc = decode_entities(text) if textNoEnc ~= text and load_data(links_data_module).unsupported_titles[text] then return text else return textNoEnc end end -- If no script object is provided (or if it's invalid or None), get one. local function checkScript(text, self, sc) if not check_object("script", true, sc) or sc:getCode() == "None" then return self:findBestScript(text) end return sc end local function normalize(text, sc) text = sc:fixDiscouragedSequences(text) return sc:toFixedNFD(text) end --[=[ Subfunction of iterateSectionSubstitutions(). Process an individual chunk of text according to the specifications in `substitution_data`. The input parameters are all as in the documentation of iterateSectionSubstitutions() except for `recursed`, which is set to true if we called ourselves recursively to process a script-specific setting or script-wide fallback. Returns two values: the processed text and the actual substitution data used to do the substitutions (same as the `actual_substitution_data` return value to iterateSectionSubstitutions()). ]=] local function doSubstitutions(self, text, sc, substitution_data, data_field, function_name, recursed) -- BE CAREFUL in this function because the value at any level can be `false`, which causes no processing to be done -- and blocks any further fallback processing. local actual_substitution_data = substitution_data -- If there are language-specific substitutes given in the data module, use those. if type(substitution_data) == "table" then -- If a script is specified, run this function with the script-specific data before continuing. local sc_code = sc:getCode() local has_substitution_data = false if substitution_data[sc_code] ~= nil then has_substitution_data = true if substitution_data[sc_code] then text, actual_substitution_data = doSubstitutions(self, text, sc, substitution_data[sc_code], data_field, function_name, true) end -- Hant, Hans and Hani are usually treated the same, so add a special case to avoid having to specify each one -- separately. elseif sc_code:match("^Han") and substitution_data.Hani ~= nil then has_substitution_data = true if substitution_data.Hani then text, actual_substitution_data = doSubstitutions(self, text, sc, substitution_data.Hani, data_field, function_name, true) end -- Substitution data with key 1 in the outer table may be given as a fallback. elseif substitution_data[1] ~= nil then has_substitution_data = true if substitution_data[1] then text, actual_substitution_data = doSubstitutions(self, text, sc, substitution_data[1], data_field, function_name, true) end end -- Iterate over all strings in the "from" subtable, and gsub with the corresponding string in "to". We work with -- the NFD decomposed forms, as this simplifies many substitutions. if substitution_data.from then has_substitution_data = true for i, from in ipairs(substitution_data.from) do -- Normalize each loop, to ensure multi-stage substitutions work correctly. text = sc:toFixedNFD(text) text = ugsub(text, sc:toFixedNFD(from), substitution_data.to[i] or "") end end if substitution_data.remove_diacritics then has_substitution_data = true text = sc:toFixedNFD(text) -- Convert exceptions to PUA. local remove_exceptions, substitutes = substitution_data.remove_exceptions if remove_exceptions then substitutes = {} local i = 0 for _, exception in ipairs(remove_exceptions) do exception = sc:toFixedNFD(exception) text = ugsub(text, exception, function(m) i = i + 1 local subst = u(0x80000 + i) substitutes[subst] = m return subst end) end end -- Strip diacritics. text = ugsub(text, "[" .. substitution_data.remove_diacritics .. "]", "") -- Convert exceptions back. if remove_exceptions then text = text:gsub("\242[\128-\191]*", substitutes) end end if not has_substitution_data and sc._data[data_field] then -- If language-specific sort key (etc.) is nil, fall back to script-wide sort key (etc.). text, actual_substitution_data = doSubstitutions(self, text, sc, sc._data[data_field], data_field, function_name, true) end elseif type(substitution_data) == "string" then -- If there is a dedicated function module, use that. local module = safe_require("Module:" .. substitution_data) if module then -- TODO: translit functions should take objects, not codes. -- TODO: translit functions should be called with form NFD. if function_name == "tr" then if not module[function_name] then error(("Internal error: Module [[%s]] has no function named 'tr'"):format(substitution_data)) end text = module[function_name](text, self._code, sc:getCode()) elseif function_name == "stripDiacritics" then -- FIXME, get rid of this arm after renaming makeEntryName -> stripDiacritics. if module[function_name] then text = module[function_name](sc:toFixedNFD(text), self, sc) elseif module.makeEntryName then text = module.makeEntryName(sc:toFixedNFD(text), self, sc) else error(("Internal error: Module [[%s]] has no function named 'stripDiacritics' or 'makeEntryName'" ):format(substitution_data)) end else if not module[function_name] then error(("Internal error: Module [[%s]] has no function named '%s'"):format( substitution_data, function_name)) end text = module[function_name](sc:toFixedNFD(text), self, sc) end else error("Substitution data '" .. substitution_data .. "' does not match an existing module.") end elseif substitution_data == nil and sc._data[data_field] then -- If language-specific sort key (etc.) is nil, fall back to script-wide sort key (etc.). text, actual_substitution_data = doSubstitutions(self, text, sc, sc._data[data_field], data_field, function_name, true) end -- Don't normalize to NFC if this is the inner loop or if a module returned nil. if recursed or not text then return text, actual_substitution_data end -- Fix any discouraged sequences created during the substitution process, and normalize into the final form. return sc:toFixedNFC(sc:fixDiscouragedSequences(text)), actual_substitution_data end --[=[ Split the text into sections, based on the presence of temporarily substituted formatting characters, then iterate over each section to apply substitutions (e.g. transliteration or diacritic stripping). This avoids putting PUA (Private Use Area) characters through language-specific modules, which may be unequipped for them. This function is passed the following values: * `self` (the Language object); * `text` (the text to process); * `sc` (the script of the text, which must be specified; callers should call checkScript() as needed to autodetect the script of the text if not given explicitly by the user); * `subbedChars` (an array of the same length as the text, indicating which characters have been substituted and by what, or {nil} if no substitutions are to happen); * `keepCarets` (DOCUMENT ME); * `substitution_data` (the data indicating which substitutions to apply, taken directly from `data_field` in the language's data structure in a submodule of [[Module:languages/data]]); * `data_field` (the field from which `substitution_data` was fetched, such as {"sort_key"} or {"strip_diacritics"}); * `function_name` (the name of the function to call to do the substitution, in case `substitution_data` specifies a module to do the substitution); * `notrim` (don't trim whitespace at the edges of `text`; set when computing the sort key, because whitespace at the beginning of a sort key is significant and causes the resulting page to be sorted at the beginning of the category it's in). Return three values: # the processed text; # the value of `subbedChars` that was passed in, possibly modified with additional character substitutions; will be {nil} if {nil} was passed in; # the actual substitution data that was used to apply substitutions to `text`; this may be different from the value of `substitution_data` passed in if that value recursively specified script-specific substitutions or if no substitution data could be found in the language-specific data (e.g. {nil} was passed in or a structure was passed in that had no setting for the script given in `sc`), but a script-wide fallback value was set; currently it is only used by {makeSortKey()}. ]=] local function iterateSectionSubstitutions(self, text, sc, subbedChars, keepCarets, substitution_data, data_field, function_name, notrim) local sections -- See [[Module:languages/data]]. if not find(text, "\244") or load_data(languages_data_module).substitution[self._code] == "cont" then sections = {text} else sections = split(text, "\244[\128-\143][\128-\191]*", true) end local actual_substitution_data for _, section in ipairs(sections) do -- Don't bother processing empty strings or whitespace (which may also not be handled well by dedicated -- modules). if gsub(section, "%s+", "") ~= "" then local sub, this_actual_substitution_data = doSubstitutions(self, section, sc, substitution_data, data_field, function_name) actual_substitution_data = this_actual_substitution_data -- Second round of temporary substitutions, in case any formatting was added by the main substitution -- process. However, don't do this if the section contains formatting already (as it would have had to have -- been escaped to reach this stage, and therefore should be given as raw text). if sub and subbedChars then local noSub for _, pattern in ipairs(require(languages_data_patterns_module)) do if match(section, pattern .. "%z?") then noSub = true end end if not noSub then sub, subbedChars = doTempSubstitutions(sub, subbedChars, keepCarets, true) end end if not sub then text = sub break end text = sub and gsub(text, pattern_escape(section), replacement_escape(sub), 1) or text end end if not notrim then -- Trim, unless there are only spacing characters, while ignoring any final formatting characters. -- Do not trim sort keys because spaces at the beginning are significant. text = text and text:gsub("^([\128-\191\244]*)%s+(%S)", "%1%2"):gsub("(%S)%s+([\128-\191\244]*)$", "%1%2") or nil end return text, subbedChars, actual_substitution_data end -- Process carets (and any escapes). Default to simple removal, if no pattern/replacement is given. local function processCarets(text, pattern, repl) local rep repeat text, rep = gsub(text, "\\\\(\\*^)", "\3%1") until rep == 0 return (text:gsub("\\^", "\4") :gsub(pattern or "%^", repl or "") :gsub("\3", "\\") :gsub("\4", "^")) end local function hasUnescapedCarets(text) return processCarets(text, "%^", "\1"):find("\1", 1, true) ~= nil end local function assertCaretCapitalizationAllowed(text, sc) if sc:forbidsCaretCapitalization() and hasUnescapedCarets(text) then error(("Caret (^) cannot be used to capitalize transliterations for the %s script " .. "(see caret_capitalization in [[Module:scripts/data]]).") :format(sc:getCanonicalName()), 2) end end -- Remove carets if they are used to capitalize parts of transliterations (unless they have been escaped). local function removeCarets(text, sc) if text:find("^", 1, true) then assertCaretCapitalizationAllowed(text, sc) if sc:hasCaretCapitalization() then return processCarets(text) end end return text end local Language = {} --[==[ Return the language code of the language. Example: {fr"} for French. ]==] function Language:getCode() return self._code end --[==[ Return the canonical name of the language. This is the name used to represent that language on Wiktionary, and is guaranteed to be unique to that language alone. Example: {"French"} for French. ]==] function Language:getCanonicalName() local name = self._name if name == nil then name = self._data[1] self._name = name end return name end --[==[ Return the display form of the language. The display form of a language, family or script is the form it takes when appearing as the <code><var>source</var></code> in categories such as <code>English terms derived from <var>source</var></code> or <code>English given names from <var>source</var></code>, and is also the displayed text in `##makeCategoryLink()` links. For full and etymology-only languages, this is the same as the canonical name, but for families, it reads <code>"<var>name</var> languages"</code> (e.g. {"Indo-Iranian languages"}), and for scripts, it reads <code>"<var>name</var> script"</code> (e.g. {"Arabic script"}). ]==] function Language:getDisplayForm() local form = self._displayForm if form == nil then form = self:getCanonicalName() -- Add article and " substrate" to substrates that lack them. if self:getFamilyCode() == "qfa-sub" then if not (sub(form, 1, 4) == "the " or sub(form, 1, 2) == "a ") then form = "a " .. form end if not match(form, " [Ss]ubstrate") then form = form .. " substrate" end end self._displayForm = form end return form end --[==[ Return the value which should be used in the HTML `lang=` attribute for tagged text in the language. ]==] function Language:getHTMLAttribute(sc, region) local code = self._code if not find(code, "-", 1, true) then return code .. "-" .. sc:getCode() .. (region and "-" .. region or "") end local parent = self:getParent() region = region or match(code, "%f[%u][%u-]+%f[%U]") if parent then return parent:getHTMLAttribute(sc, region) end -- TODO: ISO family codes can also be used. return "mis-" .. sc:getCode() .. (region and "-" .. region or "") end --[==[ Return a list of the aliases that the language is known by, excluding the canonical name. Aliases are synonyms for the language in question. The names are not guaranteed to be unique, in that sometimes more than one language is known by the same name. Example: { {"High German", "New High German", "Deutsch"}} for {{lnl|de}}. ]==] function Language:getAliases() self:loadInExtraData() return require(language_like_module).getAliases(self) end --[==[ Return a list of the known subvarieties of a given language, excluding subvarieties that have been given explicit etymology-only language codes. The names are not guaranteed to be unique, in that sometimes a given name refers to a subvariety of more than one language. Example: { {"Southern Aymara", "Central Aymara"}} for {{lnl|ay}}. Note that the returned value can have nested tables in it, when a subvariety goes by more than one name. Example: { {"North Azerbaijani", "South Azerbaijani", {"Afshar", "Afshari", "Afshar Azerbaijani", "Afchar"}, {"Qashqa'i", "Qashqai", "Kashkay"}, "Sonqor"}} for {{lnl|az}}. Here, for example, Afshar, Afshari, Afshar Azerbaijani and Afchar all refer to the same subvariety, whose preferred name is Afshar (the one listed first). To avoid a return value with nested tables in it, specify a non-{nil} value for the `flatten` parameter; in that case, the return value would be { {"North Azerbaijani", "South Azerbaijani", "Afshar", "Afshari", "Afshar Azerbaijani", "Afchar", "Qashqa'i", "Qashqai", "Kashkay", "Sonqor"}}. ]==] function Language:getVarieties(flatten) self:loadInExtraData() return require(language_like_module).getVarieties(self, flatten) end --[==[ Return a table of the "other names" that the language is known by, which are listed in the `other_names` field in the language's extra-data module. It should be noted that the `other_names` field itself is deprecated, and entries listed there should eventually be moved to either `aliases` or `varieties`, or removed if they refer to larger entities which the language in question is actually a part of. ]==] function Language:getOtherNames() -- To be eventually removed, once there are no more uses of the `other_names` field. self:loadInExtraData() return require(language_like_module).getOtherNames(self) end --[==[ Return a combined table of the canonical name, aliases, varieties and other names of a given language. ]==] function Language:getAllNames() self:loadInExtraData() return require(language_like_module).getAllNames(self) end --[==[ Return a table of types as a set (with the types as keys). The possible types are * {language}: This is a language, either full or etymology-only. * {full}: This is a "full" (not etymology-only) language, i.e. the union of {regular}, {reconstructed} and {appendix-constructed}. Note that the types {full} and {etymology-only} also exist for families, so if you want to check specifically for a full language and you have an object that might be a family, you should use {hasType("language", "full")} and not simply {hasType("full")}. * {etymology-only}: This is an etymology-only (not full) language, whose parent is another etymology-only language or a full language. Note that the types {full} and {etymology-only} also exist for families, so if you want to check specifically for an etymology-only language and you have an object that might be a family, you should use {hasType("language", "etymology-only")} and not simply {hasType("etymology-only")}. * {regular}: This indicates a full language that is attested according to [[WT:CFI]] and therefore permitted in the main namespace. There may also be reconstructed terms for the language, which are placed in the {Reconstruction} namespace and must be prefixed with `*` to indicate a reconstruction. Most full languages are natural (not constructed) languages, but a few constructed languages (e.g. Esperanto and Volapük, among others) are also allowed in the mainspace and considered regular languages. * {reconstructed}: This language is not attested according to [[WT:CFI]], and therefore is allowed only in the {Reconstruction} namespace. All terms in this language are reconstructed, and must be prefixed with `*`. Languages such as Proto-Indo-European and Proto-Germanic are in this category. '''Exception:''' Reconstructed languages can have entries in the mainspace using the ''anti-asterisk'' feature. This requires that both the headword for the mainspace term (as specified using {{para|head}}) and links to the term are prefixed with the anti-asterisk indicator `!!`. * {appendix-constructed}: This language is attested but does not meet the additional requirements set out for constructed languages ([[WT:CFI#Constructed languages]]). Its entries must therefore be in the Appendix namespace, but they are not reconstructed and therefore should not have * prefixed in links. ]==] function Language:getTypes() local types = self._types if types == nil then types = {language = true} if self:getFullCode() == self._code then types.full = true else types["etymology-only"] = true end for t in gmatch(self._data.type, "[^,]+") do types[t] = true end self._types = types end return types end --[==[ Given a list of types as strings, return {true} if the language has all of them. ]==] function Language:hasType(...) Language.hasType = require(language_like_module).hasType return self:hasType(...) end --[==[ Return a list containing `WikimediaLanguage` objects (see [[Module:wikimedia languages]]), which represent languages and their codes as they are used in Wikimedia projects for interwiki linking and such. More than one object may be returned, as a single Wiktionary language may correspond to multiple Wikimedia languages. For example, Wiktionary's single code `sh` (Serbo-Croatian) maps to four Wikimedia codes: `sh` (Serbo-Croatian), `bs` (Bosnian), `hr` (Croatian) and `sr` (Serbian). The code for the Wikimedia language is retrieved from the `wikimedia_codes` property in the data modules. If that property is not present, the code of the current language is used. If none of the available codes is actually a valid Wikimedia code, an empty list is returned. ]==] function Language:getWikimediaLanguages() local wm_langs = self._wikimediaLanguageObjects if wm_langs == nil then local codes = self:getWikimediaLanguageCodes() wm_langs = {} for i = 1, #codes do wm_langs[i] = get_wikimedia_lang(codes[i]) end self._wikimediaLanguageObjects = wm_langs end return wm_langs end function Language:getWikimediaLanguageCodes() local wm_langs = self._wikimediaLanguageCodes if wm_langs == nil then wm_langs = self._data.wikimedia_codes if wm_langs then wm_langs = split(wm_langs, ",", true, true) else local code = self._code if is_known_language_tag(code) then wm_langs = {code} else -- Inherit, but only if no codes are specified in the data *and* -- the language code isn't a valid Wikimedia language code. local parent = self:getParent() wm_langs = parent and parent:getWikimediaLanguageCodes() or {} end end self._wikimediaLanguageCodes = wm_langs end return wm_langs end --[==[ Return the name of the Wikipedia article for the language. `project` specifies the language and project to retrieve the article from, defaulting to {"enwiki"} for the English Wikipedia. Normally if specified it should be the project code for a specific-language Wikipedia e.g. "zhwiki" for the Chinese Wikipedia, but it can be any project, including non-Wikipedia ones. If the project is the English Wikipedia and the property {wikipedia_article} is present in the data module it will be used first. In all other cases, a sitelink will be generated from {:getWikidataItem} (if set). The resulting value (or lack of value) is cached so that subsequent calls are fast. If no value could be determined, and `noCategoryFallback` is {false}, `##Language:getCategoryName` is used as fallback; otherwise, {nil} is returned. Note that if `noCategoryFallback` is {nil} or omitted, it defaults to {false} if the project is the English Wikipedia, otherwise to {true}. In other words, under normal circumstances, if the English Wikipedia article couldn't be retrieved, the return value will fall back to a link to the language's category, but this won't normally happen for any other project. ]==] function Language:getWikipediaArticle(noCategoryFallback, project) Language.getWikipediaArticle = require(language_like_module).getWikipediaArticle return self:getWikipediaArticle(noCategoryFallback, project) end function Language:makeWikipediaLink() return make_link(self, "w:" .. self:getWikipediaArticle(), self:getCanonicalName()) end --[==[ Return the name of the Wikimedia Commons category page for the language. ]==] function Language:getCommonsCategory() Language.getCommonsCategory = require(language_like_module).getCommonsCategory return self:getCommonsCategory() end --[==[ Return the Wikidata item ID for the language or {nil}. This corresponds to the the second field in the data modules. ]==] function Language:getWikidataItem() Language.getWikidataItem = require(language_like_module).getWikidataItem return self:getWikidataItem() end --[==[ Return a list of `Script` objects for all scripts that the language is written in. See [[Module:scripts]]. ]==] function Language:getScripts() local scripts = self._scriptObjects if scripts == nil then local codes = self:getScriptCodes() if codes[1] == "All" then scripts = load_data(scripts_data_module) else scripts = {} for i = 1, #codes do scripts[i] = get_script(codes[i]) end end self._scriptObjects = scripts end return scripts end --[==[ Return the list of script codes in the language's data file. ]==] function Language:getScriptCodes() local scripts = self._scriptCodes if scripts == nil then scripts = self._data[4] if scripts then local codes, n = {}, 0 for code in gmatch(scripts, "[^,]+") do n = n + 1 -- Special handling of "Hants", which represents "Hani", "Hant" and "Hans" collectively. if code == "Hants" then codes[n] = "Hani" codes[n + 1] = "Hant" codes[n + 2] = "Hans" n = n + 2 else codes[n] = code end end scripts = codes else scripts = {"None"} end self._scriptCodes = scripts end return scripts end --[==[ Given some text, iterate through the scripts of a given language trying to find the script that best matches the text. Return a {Script} object representing the script. If no match is found at all, it returns the {None} script object. ]==] function Language:findBestScript(text, forceDetect) if not text or text == "" or text == "-" then return get_script("None") end -- Differs from table returned by getScriptCodes, as Hants is not normalized into its constituents. local codes = self._bestScriptCodes if codes == nil then codes = self._data[4] codes = codes and split(codes, ",", true, true) or {"None"} self._bestScriptCodes = codes end local first_sc = codes[1] if first_sc == "All" then return find_best_script_without_lang(text) end local codes_len = #codes if not (forceDetect or first_sc == "Hants" or codes_len > 1) then first_sc = get_script(first_sc) local charset = first_sc.characters return charset and umatch(text, "[" .. charset .. "]") and first_sc or get_script("None") end -- Remove all formatting characters. text = get_plaintext(text) -- Remove all spaces and any ASCII punctuation. Some non-ASCII punctuation is script-specific, so can't be removed. text = ugsub(text, "[%s!\"#%%&'()*,%-./:;?@[\\%]_{}]+", "") if #text == 0 then return get_script("None") end -- Try to match every script against the text, -- and return the one with the most matching characters. local bestcount, bestscript, length = 0 for i = 1, codes_len do local sc = codes[i] -- Special case for "Hants", which is a special code that represents whichever of "Hant" or "Hans" best matches, -- or "Hani" if they match equally. This avoids having to list all three. In addition, "Hants" will be treated -- as the best match if there is at least one matching character, under the assumption that a Han script is -- desirable in terms that contain a mix of Han and other scripts (not counting those which use Jpan or Kore). if sc == "Hants" then local Hani = get_script("Hani") if not Hant_chars then Hant_chars = load_data("Module:zh/data/ts") Hans_chars = load_data("Module:zh/data/st") end local t, s, found = 0, 0 -- This is faster than using mw.ustring.gmatch directly. for ch in gmatch((ugsub(text, "[" .. Hani.characters .. "]", "\255%0")), "\255(.[\128-\191]*)") do found = true if Hant_chars[ch] then t = t + 1 if Hans_chars[ch] then s = s + 1 end elseif Hans_chars[ch] then s = s + 1 else t, s = t + 1, s + 1 end end if found then if t == s then return Hani end return get_script(t > s and "Hant" or "Hans") end else sc = get_script(sc) if not length then length = ulen(text) end -- Count characters by removing everything in the script's charset and comparing to the original length. local charset = sc.characters local count = charset and length - ulen((ugsub(text, "[" .. charset .. "]+", ""))) or 0 if count >= length then return sc elseif count > bestcount then bestcount = count bestscript = sc end end end -- Return best matching script, or otherwise None. return bestscript or get_script("None") end --[==[ Return a `Family` object for the language family that the language belongs to. See [[Module:families]]. ]==] function Language:getFamily() local family = self._familyObject if family == nil then family = self:getFamilyCode() -- If the value is nil, it's cached as false. family = family and get_family(family) or false self._familyObject = family end return family or nil end --[==[ Return the family code in the language's data file. ]==] function Language:getFamilyCode() local family = self._familyCode if family == nil then -- If the value is nil, it's cached as false. family = self._data[3] or false self._familyCode = family end return family or nil end function Language:getFamilyName() local family = self._familyName if family == nil then family = self:getFamily() -- If the value is nil, it's cached as false. family = family and family:getCanonicalName() or false self._familyName = family end return family or nil end do local function check_family(self, family) if type(family) == "table" then family = family:getCode() end if self:getFamilyCode() == family then return true end local self_family = self:getFamily() if self_family:inFamily(family) then return true -- If the family isn't a real family (e.g. creoles) check any ancestors. elseif self_family:inFamily("qfa-not") then local ancestors = self:getAncestors() for _, ancestor in ipairs(ancestors) do if ancestor:inFamily(family) then return true end end end end --[==[ Check whether the language belongs to `family` (which can be a family code or object). A list of objects can be given in place of `family`; in that case, return true if the language belongs to any of the specified families. Note that some languages (in particular, certain creoles) can have multiple immediate ancestors potentially belonging to different families; in that case, return true if the language belongs to any of the specified families. ]==] function Language:inFamily(...) if self:getFamilyCode() == nil then return false end return check_inputs(self, check_family, false, ...) end end function Language:getParent() local parent = self._parentObject if parent == nil then parent = self:getParentCode() -- If the value is nil, it's cached as false. parent = parent and get_by_code(parent, nil, true, true) or false self._parentObject = parent end return parent or nil end function Language:getParentCode() local parent = self._parentCode if parent == nil then -- If the value is nil, it's cached as false. parent = self._data.parent or false self._parentCode = parent end return parent or nil end function Language:getParentName() local parent = self._parentName if parent == nil then parent = self:getParent() -- If the value is nil, it's cached as false. parent = parent and parent:getCanonicalName() or false self._parentName = parent end return parent or nil end function Language:getParentChain() local chain = self._parentChain if chain == nil then chain = {} local parent, n = self:getParent(), 0 while parent do n = n + 1 chain[n] = parent parent = parent:getParent() end self._parentChain = chain end return chain end --[==[ Return a list of the pseudo-families (if any) that a language belongs to. The returned value is a list of Family objects. Pseudo-families are groupings of languages that are not clades but share some geographic, ethnic or other characteristic (e.g. [[:Category:Papuan languages]], [[:Category:Jewish languages]]). ]==] function Language:getPseudoFamilies() self:loadInExtraData() return require(language_like_module).getPseudoFamilies(self) end do local function check_lang(self, lang) for _, parent in ipairs(self:getParentChain()) do if (type(lang) == "string" and lang or lang:getCode()) == parent:getCode() then return true end end end function Language:hasParent(...) return check_inputs(self, check_lang, false, ...) end end --[==[ If the language is etymology-only, iterate through parents until a full language or family is found, and the corresponding object is returned. If the language is a full language, return that language. ]==] function Language:getFull() local full = self._fullObject if full == nil then full = self:getFullCode() full = full == self._code and self or get_by_code(full) self._fullObject = full end return full end --[==[ If the language is an etymology-only language, iterate through parents until a full language or family is found, and the corresponding code is returned. If the language is a full language, return that language's code. ]==] function Language:getFullCode() return self._fullCode or self._code end --[==[ If the language is an etymology-only language, iterate through parents until a full language or family is found, and the corresponding canonical name is returned. If the language is a full language, return that language's canonical name. ]==] function Language:getFullName() local full = self._fullName if full == nil then full = self:getFull():getCanonicalName() self._fullName = full end return full end --[==[ Return a list of {Language} objects for all languages that this language is directly descended from. Generally this is only a single language, but creoles, pidgins and mixed languages can have multiple ancestors. ]==] function Language:getAncestors() local ancestors = self._ancestorObjects if ancestors == nil then ancestors = {} local ancestor_codes = self:getAncestorCodes() if #ancestor_codes > 0 then for _, ancestor in ipairs(ancestor_codes) do insert(ancestors, get_by_code(ancestor, nil, true)) end else local fam = self:getFamily() local protoLang = fam and fam:getProtoLanguage() or nil -- For the cases where the current language is the proto-language -- of its family, or an etymology-only language that is ancestral to that -- proto-language, we need to step up a level higher right from the -- start. if protoLang and ( protoLang:getCode() == self._code or (self:hasType("etymology-only") and protoLang:hasAncestor(self)) ) then fam = fam:getFamily() protoLang = fam and fam:getProtoLanguage() or nil end while not protoLang and not (not fam or fam:getCode() == "qfa-not") do fam = fam:getFamily() protoLang = fam and fam:getProtoLanguage() or nil end insert(ancestors, protoLang) end self._ancestorObjects = ancestors end return ancestors end do -- Avoid a language being its own ancestor via class inheritance. We only need to check for this if the language has -- inherited an ancestor table from its parent, because we never want to drop ancestors that have been explicitly -- set in the data. Recursively iterate over ancestors until we either find self or run out. If self is found, -- return true. local function check_ancestor(self, lang) local codes = lang:getAncestorCodes() if not codes then return nil end for i = 1, #codes do local code = codes[i] if code == self._code then return true end local anc = get_by_code(code, nil, true) if check_ancestor(self, anc) then return true end end end --[==[ Return a list of `Language` codes for all languages that this language is directly descended from. Generally this is only a single language, but creoles, pidgins and mixed languages can have multiple ancestors. ]==] function Language:getAncestorCodes() if self._ancestorCodes then return self._ancestorCodes end local data = self._data local codes = data.ancestors if codes == nil then codes = {} self._ancestorCodes = codes return codes end codes = split(codes, ",", true, true) self._ancestorCodes = codes -- If there are no codes or the ancestors weren't inherited data, there's nothing left to check. if #codes == 0 or self:getData(false, "raw").ancestors ~= nil then return codes end local i = 1 while i <= #codes do if check_ancestor(self, self) then remove(codes, i) else i = i + 1 end end return codes end end --[==[ Given a list of language objects or codes, return true if at least one of them is an ancestor. This includes any etymology-only children of that ancestor. If the language's ancestor(s) are etymology-only languages, it will also return true for those language parent(s) (e.g. if Vulgar Latin is the ancestor, it will also return true for its parent, Latin). However, a parent is excluded from this if the ancestor is also ancestral to that parent (e.g. if Classical Persian is the ancestor, Persian would return {false}, because Classical Persian is also ancestral to Persian). ]==] function Language:hasAncestor(...) local function iterateOverAncestorTree(node, func, parent_check) local ancestors = node:getAncestors() local ancestorsParents = {} for _, ancestor in ipairs(ancestors) do -- When checking the parents of the other language, and the ancestor is also a parent, skip to the next -- ancestor, so that we exclude any etymology-only children of that parent that are not directly related -- (see below). local ret = (parent_check or not node:hasParent(ancestor)) and func(ancestor) or iterateOverAncestorTree(ancestor, func, parent_check) if ret then return ret end end -- Check the parents of any ancestors. We don't do this if checking the parents of the other language, so that -- we exclude any etymology-only children of those parents that are not directly related (e.g. if the ancestor -- is Vulgar Latin and we are checking New Latin, we want it to return false because they are on different -- ancestral branches. As such, if we're already checking the parent of New Latin (Latin) we don't want to -- compare it to the parent of the ancestor (Latin), as this would be a false positive; it should be one or the -- other). if not parent_check then return nil end for _, ancestor in ipairs(ancestors) do local ancestorParents = ancestor:getParentChain() for _, ancestorParent in ipairs(ancestorParents) do if ancestorParent:getCode() == self._code or ancestorParent:hasAncestor(ancestor) then break else insert(ancestorsParents, ancestorParent) end end end for _, ancestorParent in ipairs(ancestorsParents) do local ret = func(ancestorParent) if ret then return ret end end end local function do_iteration(otherlang, parent_check) -- otherlang can't be self if (type(otherlang) == "string" and otherlang or otherlang:getCode()) == self._code then return false end repeat if iterateOverAncestorTree( self, function(ancestor) return ancestor:getCode() == (type(otherlang) == "string" and otherlang or otherlang:getCode()) end, parent_check ) then return true elseif type(otherlang) == "string" then otherlang = get_by_code(otherlang, nil, true) end otherlang = otherlang:getParent() parent_check = false until not otherlang end local parent_check = true for _, otherlang in ipairs{...} do local ret = do_iteration(otherlang, parent_check) if ret then return true end end return false end do local function construct_node(lang, memo) local branch, ancestors = {lang = lang:getCode()} memo[lang:getCode()] = branch for _, ancestor in ipairs(lang:getAncestors()) do if ancestors == nil then ancestors = {} end insert(ancestors, memo[ancestor:getCode()] or construct_node(ancestor, memo)) end branch.ancestors = ancestors return branch end function Language:getAncestorChain() local chain = self._ancestorChain if chain == nil then chain = construct_node(self, {}) self._ancestorChain = chain end return chain end end function Language:getAncestorChainOld() local chain = self._ancestorChain if chain == nil then chain = {} local step = self while true do local ancestors = step:getAncestors() step = #ancestors == 1 and ancestors[1] or nil if not step then break end insert(chain, step) end self._ancestorChain = chain end return chain end local function fetch_descendants(self, fmt) local descendants, family = {}, self:getFamily() -- Iterate over all three datasets. for _, data in ipairs{ require("Module:languages/code to canonical name"), require("Module:etymology languages/code to canonical name"), require("Module:families/code to canonical name"), } do for code in pairs(data) do local lang = get_by_code(code, nil, true, true) if not lang then error(("Internal error: code '%s' can't be fetched"):format(code)) end -- Test for a descendant. Earlier tests weed out most candidates, while the more intensive tests are only used sparingly. if ( code ~= self._code and -- Not self. lang:inFamily(family) and -- In the same family. ( family:getProtoLanguageCode() == self._code or -- Self is the protolanguage. self:hasDescendant(lang) or -- Full hasDescendant check. (lang:getFullCode() == self._code and not self:hasAncestor(lang)) -- Etymology-only child which isn't an ancestor. ) ) then if fmt == "object" then insert(descendants, lang) elseif fmt == "code" then insert(descendants, code) elseif fmt == "name" then insert(descendants, lang:getCanonicalName()) end end end end return descendants end function Language:getDescendants() local descendants = self._descendantObjects if descendants == nil then descendants = fetch_descendants(self, "object") self._descendantObjects = descendants end return descendants end function Language:getDescendantCodes() local descendants = self._descendantCodes if descendants == nil then descendants = fetch_descendants(self, "code") self._descendantCodes = descendants end return descendants end function Language:getDescendantNames() local descendants = self._descendantNames if descendants == nil then descendants = fetch_descendants(self, "name") self._descendantNames = descendants end return descendants end do local function check_lang(self, lang) if type(lang) == "string" then lang = get_by_code(lang, nil, true) end if lang:hasAncestor(self) then return true end end function Language:hasDescendant(...) return check_inputs(self, check_lang, false, ...) end end local function fetch_children(self, fmt) local m_etym_data = require(etymology_languages_data_module) local self_code, children = self._code, {} for code, lang in pairs(m_etym_data) do local _lang = lang repeat local parent = _lang.parent if parent == self_code then if fmt == "object" then insert(children, get_by_code(code, nil, true)) elseif fmt == "code" then insert(children, code) elseif fmt == "name" then insert(children, lang[1]) end break end _lang = m_etym_data[parent] until not _lang end return children end function Language:getChildren() local children = self._childObjects if children == nil then children = fetch_children(self, "object") self._childObjects = children end return children end function Language:getChildrenCodes() local children = self._childCodes if children == nil then children = fetch_children(self, "code") self._childCodes = children end return children end function Language:getChildrenNames() local children = self._childNames if children == nil then children = fetch_children(self, "name") self._childNames = children end return children end function Language:hasChild(...) local lang = ... if not lang then return false elseif type(lang) == "string" then lang = get_by_code(lang, nil, true) end if lang:hasParent(self) then return true end return self:hasChild(select(2, ...)) end --[==[ Return the name of the main category of that language. Example: {French language"} for French, whose category is at [[:Category:French language]]. Unless optional argument `nocap` is given, the language name at the beginning of the returned value will be capitalized. This capitalization is correct for category names, but not if the language name is lowercase and the returned value of this function is used in the middle of a sentence. ]==] function Language:getCategoryName(nocap) local name = self._categoryName if name == nil then name = self:getCanonicalName() -- If a substrate, omit any leading article. if self:getFamilyCode() == "qfa-sub" then name = name:gsub("^the ", ""):gsub("^a ", "") end -- Only add " language" if a full language. if self:hasType("full") then -- Unless the canonical name already ends with "language", "lect" or their derivatives, add " language". if not (match(name, "[Ll]anguage$") or match(name, "[Ll]ect$")) then name = name .. " language" end end self._categoryName = name end if nocap then return name end return mw.getContentLanguage():ucfirst(name) end --[==[ Create a link to the category; the link text is the canonical name. ]==] function Language:makeCategoryLink() return make_link(self, ":Category:" .. self:getCategoryName(), self:getDisplayForm()) end function Language:getStandardCharacters(sc) local standard_chars = self._data.standard_chars if type(standard_chars) ~= "table" then return standard_chars elseif sc and type(sc) ~= "string" then check_object("script", nil, sc) sc = sc:getCode() end if (not sc) or sc == "None" then local scripts = {} for _, script in pairs(standard_chars) do insert(scripts, script) end return concat(scripts) end if standard_chars[sc] then return standard_chars[sc] .. (standard_chars[1] or "") end end --[==[ Strip diacritics from display text `text` (in a language-specific fashion), which is in the script `sc`. If `sc` is omitted or {nil}, the script is autodetected. This also strips certain punctuation characters from the end and (in the case of Spanish upside-down question mark and exclamation points) from the beginning; strips any whitespace at the end of the text or between the text and final stripped punctuation characters; and applies some language-specific Unicode normalizations to replace discouraged characters with their prescribed alternatives. Return the stripped text. ]==] function Language:stripDiacritics(text, sc) if (not text) or text == "" then return text end sc = checkScript(text, self, sc) text = normalize(text, sc) -- FIXME, rename makeEntryName to stripDiacritics and get rid of second and third return values -- everywhere local _ text, _, _ = iterateSectionSubstitutions(self, text, sc, nil, nil, self._data.strip_diacritics or self._data.entry_name, "strip_diacritics", "stripDiacritics") text = umatch(text, "^[¿¡]?(.-[^%s%p].-)%s*[؟?!;՛՜ ՞ ՟?!︖︕।॥။၊་།]?$") or text return text end --[==[ Convert a ''logical'' pagename (the pagename as it appears to the user, after diacritics and punctuation have been stripped) to a ''physical'' pagename (the pagename as it appears in the MediaWiki database). Reasons for a difference between the two are (a) unsupported titles such as `[ ]` (with square brackets in them), `#` (pound/hash sign) and `¯\_(ツ)_/¯` (with underscores), as well as overly long titles of various sorts; (b) "mammoth" pages that are split into parts (e.g. `a`, which is split into physical pagenames `a/languages A to L` and `a/languages M to Z`). For almost all purposes, you should work with logical and not physical pagenames. But there are certain use cases that require physical pagenames, such as checking the existence of a page or retrieving a page's contents. `pagename` is the logical pagename to be converted. `is_reconstructed_or_appendix` indicates whether the page is in the `Reconstruction` or `Appendix` namespaces. If it is omitted or has the value {nil}, the pagename is checked for an initial asterisk, and if found, the page is assumed to be a `Reconstruction` page. Setting a value of `false` or `true` to `is_reconstructed_or_appendix` disables this check and allows for mainspace pagenames that begin with an asterisk. ]==] function Language:logicalToPhysical(pagename, is_reconstructed_or_appendix) -- FIXME: This probably shouldn't happen but it happens when makeEntryName() receives nil. if pagename == nil then track("nil-passed-to-logicalToPhysical") return nil end local initial_asterisk if is_reconstructed_or_appendix == nil then local pagename_minus_initial_asterisk initial_asterisk, pagename_minus_initial_asterisk = pagename:match("^(%*)(.*)$") if pagename_minus_initial_asterisk then is_reconstructed_or_appendix = true pagename = pagename_minus_initial_asterisk elseif self:hasType("appendix-constructed") then is_reconstructed_or_appendix = true end end if not is_reconstructed_or_appendix then -- Check if the pagename is a listed unsupported title. local unsupportedTitles = load_data(links_data_module).unsupported_titles if unsupportedTitles[pagename] then return "Unsupported titles/" .. unsupportedTitles[pagename] end end -- Set `unsupported` as true if certain conditions are met. local unsupported -- Check if there's an unsupported character. \239\191\189 is the replacement character U+FFFD, which can't be typed -- directly here due to an abuse filter. Unix-style dot-slash notation is also unsupported, as it is used for -- relative paths in links, as are 3 or more consecutive tildes. Note: match is faster with magic -- characters/charsets; find is faster with plaintext. if ( match(pagename, "[#<>%[%]_{|}]") or find(pagename, "\239\191\189") or match(pagename, "%f[^%z/]%.%.?%f[%z/]") or find(pagename, "~~~") ) then unsupported = true -- If it looks like an interwiki link. elseif find(pagename, ":") then local prefix = gsub(pagename, "^:*(.-):.*", ulower) if ( load_data("Module:data/namespaces")[prefix] or load_data("Module:data/interwikis")[prefix] ) then unsupported = true end end -- Escape unsupported characters so they can be used in titles. ` is used as a delimiter for this, so a raw use of -- it in an unsupported title is also escaped here to prevent interference; this is only done with unsupported -- titles, though, so inclusion won't in itself mean a title is treated as unsupported (which is why it's excluded -- from the earlier test). if unsupported then -- FIXME: This conversion needs to be different for reconstructed pages with unsupported characters. There -- aren't any currently, but if there ever are, we need to fix this e.g. to put them in something like -- Reconstruction:Proto-Indo-European/Unsupported titles/`lowbar``num`. local unsupported_characters = load_data(links_data_module).unsupported_characters pagename = pagename:gsub("[#<>%[%]_`{|}\239]\191?\189?", unsupported_characters) :gsub("%f[^%z/]%.%.?%f[%z/]", function(m) return (gsub(m, "%.", "`period`")) end) :gsub("~~~+", function(m) return (gsub(m, "~", "`tilde`")) end) pagename = "Unsupported titles/" .. pagename elseif not is_reconstructed_or_appendix then -- Check if this is a mammoth page. If so, which subpage should we link to? local m_links_data = load_data(links_data_module) local mammoth_page_type = m_links_data.mammoth_pages[pagename] if mammoth_page_type then local canonical_name = self:getFullName() if canonical_name ~= "Translingual" and canonical_name ~= "English" then local this_subpage local L2_sort_key = get_L2_sort_key(canonical_name) for _, subpage_spec in ipairs(m_links_data.mammoth_page_subpage_types[mammoth_page_type]) do -- unpack() fails utterly on data loaded using mw.loadData() even if offsets are given local subpage, pattern = subpage_spec[1], subpage_spec[2] if pattern == true or L2_sort_key:match(pattern) then this_subpage = subpage break end end if not this_subpage then error(("Internal error: Bad data in mammoth_page_subpage_pages in [[Module:links/data]] for mammoth page %s, type %s; last entry didn't have 'true' in it"):format( pagename, mammoth_page_type)) end pagename = pagename .. "/" .. this_subpage end end end return (initial_asterisk or "") .. pagename end --[==[ Strip the diacritics from a display pagename and convert the resulting logical pagename into a physical pagename. This allows you, for example, to retrieve the contents of the page or check its existence. WARNING: This is deprecated and will be going away. It is a simple composition of `##Language:stripDiacritics` and `##Language:logicalToPhysical`; most callers only want the former, and if you need both, call them both yourself. `text` and `sc` are as in `##Language:stripDiacritics`, and `is_reconstructed_or_appendix` is as in `##Language:logicalToPhysical`. ]==] function Language:makeEntryName(text, sc, is_reconstructed_or_appendix) track("makeEntryName called") return self:logicalToPhysical(self:stripDiacritics(text, sc), is_reconstructed_or_appendix) end --[==[ Generate term alternants (e.g. simplified versions of traditional Chinese terms) using a language-specific method, and return them as a table. If the language provides no method for generating alternants, return a table containing only the input term. ]==] function Language:generateAlternants(text, sc) local generate_alternants = self._data.generate_alternants if generate_alternants == nil then return {text} end sc = checkScript(text, self, sc) return require("Module:" .. generate_alternants).generateAlternants(text, self, sc) end --[==[ Create a sort key for the given stripped text, following the rules appropriate for the language. This removes diacritical marks from the stripped text if they are not considered significant for sorting, and may perform some other changes. Any initial hyphen is also removed, and anything in parentheses is removed as well. The `sort_key` setting for each language in the data modules defines the replacements made by this function, or it gives the name of the module that takes the stripped text and returns a sortkey. ]==] function Language:makeSortKey(text, sc) if (not text) or text == "" then return text end if match(text, "<[^<>]+>") then track("track HTML tag") end -- Remove directionality/control characters, bold, italics, soft hyphens, strip markers and HTML tags. -- FIXME: Partly duplicated with remove_formatting() in [[Module:links]]. text = ugsub(text, "[\194\173\216\156\226\128\142\226\128\143\226\128\170-\226\128\174\226\129\166-\226\129\175]", "") text = text:gsub("('*)'''(.-'*)'''", "%1%2"):gsub("('*)''(.-'*)''", "%1%2") text = gsub(unstrip(text), "<[^<>]+>", "") text = decode_uri(text, "PATH") text = checkNoEntities(text) -- Remove initial hyphens and * unless the term only consists of spacing + punctuation characters. text = ugsub(text, "^([􀀀-􏿽]*)[-־ـ᠊*]+([􀀀-􏿽]*)(.*[^%s%p].*)", "%1%2%3") sc = checkScript(text, self, sc) text = normalize(text, sc) text = removeCarets(text, sc) -- For languages with dotted dotless i, ensure that "İ" is sorted as "i", and "I" is sorted as "ı". if self:hasDottedDotlessI() then text = gsub(text, "I\204\135", "i") -- decomposed "İ" :gsub("I", "ı") text = sc:toFixedNFD(text) end -- Convert to lowercase, make the sortkey, then convert to uppercase. Where the language has dotted dotless i, it is -- usually not necessary to convert "i" to "İ" and "ı" to "I" first, because "I" will always be interpreted as -- conventional "I" (not dotless "İ") by any sorting algorithms, which will have been taken into account by the -- sortkey substitutions themselves. However, if no sortkey substitutions have been specified, then conversion is -- necessary so as to prevent "i" and "ı" both being sorted as "I". -- -- An exception is made for scripts that (sometimes) sort by scraping page content, as that means they are sensitive -- to changes in capitalization (as it changes the target page). if not sc:sortByScraping() then text = ulower(text) end local actual_substitution_data, _ -- Don't trim whitespace here because it's significant at the beginning of a sort key or sort base. text, _, actual_substitution_data = iterateSectionSubstitutions(self, text, sc, nil, nil, self._data.sort_key, "sort_key", "makeSortKey", "notrim") if not sc:sortByScraping() then if self:hasDottedDotlessI() and not actual_substitution_data then text = text:gsub("ı", "I"):gsub("i", "İ") text = sc:toFixedNFC(text) end text = uupper(text) end -- Remove parentheses, as long as they are either preceded or followed by something. text = gsub(text, "(.)[()]+", "%1"):gsub("[()]+(.)", "%1") text = escape_risky_characters(text) return text end --[==[ Create the form used as as a basis for display text and transliteration. FIXME: Rename to correctInputText(). ]==] local function processDisplayText(text, self, sc, keepCarets, keepPrefixes) local subbedChars = {} text, subbedChars = doTempSubstitutions(text, subbedChars, keepCarets) text = decode_uri(text, "PATH") text = checkNoEntities(text) sc = checkScript(text, self, sc) text = normalize(text, sc) text, subbedChars = iterateSectionSubstitutions(self, text, sc, subbedChars, keepCarets, self._data.display_text, "display_text", "makeDisplayText") text = removeCarets(text, sc) -- Remove any interwiki link prefixes (unless they have been escaped or this has been disabled). if find(text, ":") and not keepPrefixes then local rep repeat text, rep = gsub(text, "\\\\(\\*:)", "\3%1") until rep == 0 text = gsub(text, "\\:", "\4") while true do local prefix = gsub(text, "^(.-):.+", function(m1) return (gsub(m1, "\244[\128-\191]*", "")) end) -- Check if the prefix is an interwiki, though ignore capitalised Wiktionary:, which is a namespace. if not prefix or prefix == text or prefix == "Wiktionary" or not (load_data("Module:data/interwikis")[ulower(prefix)] or prefix == "") then break end text = gsub(text, "^(.-):(.*)", function(m1, m2) local ret = {} for subbedChar in gmatch(m1, "\244[\128-\191]*") do insert(ret, subbedChar) end return concat(ret) .. m2 end) end text = gsub(text, "\3", "\\"):gsub("\4", ":") end return text, subbedChars end --[==[ Make the display text (i.e. what is displayed on the page). ]==] function Language:makeDisplayText(text, sc, keepPrefixes) if not text or text == "" then return text end local subbedChars text, subbedChars = processDisplayText(text, self, sc, nil, keepPrefixes) text = escape_risky_characters(text) return undoTempSubstitutions(text, subbedChars) end --[==[ Transliterate the text from the given script into Latin script (see [[Wiktionary:Transliteration and romanization]]). The language must have the `translit` property for this to work; if it is not present, {nil} is returned. The `sc` parameter is handled by the transliteration module, and how it is handled is specific to that module. Some transliteration modules may tolerate {nil} as the script, others require it to be one of the possible scripts that the module can transliterate, and will throw an error if it's not one of them. For this reason, the `sc` parameter should always be provided when writing non-language-specific code. The `module_override` parameter is used to override the default module that is used to provide the transliteration. This is useful in cases where you need to demonstrate a particular module in use, but there is no default module yet, or you want to demonstrate an alternative version of a transliteration module before making it official. It should not be used in real modules or templates, only for testing. All uses of this parameter are tracked by [[Wiktionary:Tracking/languages/module_override]]. '''Known bugs''': * This function assumes {tr(s1) .. tr(s2) == tr(s1 .. s2)}. When this assertion fails, wikitext markups like <nowiki>'''</nowiki> can cause wrong transliterations. * HTML entities like `&amp;apos;`, often used to escape wikitext markups, do not work. ]==] function Language:transliterate(text, sc, module_override) -- If there is no text, or the language doesn't have transliteration data and there's no override, return nil. if not text or text == "" or text == "-" then return text end -- If the script is not transliteratable (and no override is given), return nil. sc = checkScript(text, self, sc) if not (sc:isTransliterated() or module_override) then -- temporary tracking to see if/when this gets triggered track("non-transliterable") track("non-transliterable/" .. self._code) track("non-transliterable/" .. sc:getCode()) track("non-transliterable/" .. sc:getCode() .. "/" .. self._code) return nil end -- Remove any strip markers. text = unstrip(text) -- Do not process the formatting into PUA characters for certain languages. local processed = load_data(languages_data_module).substitution[self._code] ~= "none" -- Get the display text with the keepCarets flag set. local subbedChars if processed then text, subbedChars = processDisplayText(text, self, sc, true) end -- Transliterate (using the module override if applicable). text, subbedChars = iterateSectionSubstitutions(self, text, sc, subbedChars, true, module_override or self._data.translit, "translit", "tr") if not text then return nil end -- Incomplete transliterations return nil. local charset = sc.characters if charset and umatch(text, "[" .. charset .. "]") then -- Remove any characters in Latin, which includes Latin characters also included in other scripts (as these are -- false positives), as well as any PUA substitutions. Anything remaining should only be script code "None" -- (e.g. numerals). local check_text = ugsub(text, "[" .. get_script("Latn").characters .. "􀀀-􏿽]+", "") -- Set none_is_last_resort_only flag, so that any non-None chars will cause a script other than "None" to be -- returned. if find_best_script_without_lang(check_text, true):getCode() ~= "None" then return nil end end if processed then text = escape_risky_characters(text) text = undoTempSubstitutions(text, subbedChars) end -- If allowed for this script, capitalize transliteration letters preceded by ^ and remove the caret. if text and text:find("^", 1, true) then assertCaretCapitalizationAllowed(text, sc) if sc:hasCaretCapitalization() then text = processCarets(text, "%^([\128-\191\244]*%*?)([^\128-\191\244][\128-\191]*)", function(m1, m2) return m1 .. uupper(m2) end) end end -- Track module overrides. if module_override ~= nil then track("module_override") end return text end do local function handle_language_spec(self, spec, sc) local ret = self["_" .. spec] if ret == nil then ret = self._data[spec] if type(ret) == "string" then ret = list_to_set(split(ret, ",", true, true)) end self["_" .. spec] = ret end if type(ret) == "table" then ret = ret[sc:getCode()] end return not not ret end function Language:overrideManualTranslit(sc) return handle_language_spec(self, "override_translit", sc) end function Language:link_tr(sc) return handle_language_spec(self, "link_tr", sc) end end --[==[ Return {true} if the language has a transliteration module, or {false} if it doesn't. ]==] function Language:hasTranslit() return not not self._data.translit end --[==[ Return {true} if the language uses the letters I/ı and İ/i, or {false} if it doesn't.]==] function Language:hasDottedDotlessI() return not not self._data.dotted_dotless_i end function Language:toJSON(opts) local strip_diacritics, strip_diacritics_patterns, strip_diacritics_remove_diacritics = self._data.strip_diacritics if strip_diacritics then if strip_diacritics.from then strip_diacritics_patterns = {} for i, from in ipairs(strip_diacritics.from) do insert(strip_diacritics_patterns, {from = from, to = strip_diacritics.to[i] or ""}) end end strip_diacritics_remove_diacritics = strip_diacritics.remove_diacritics end -- mainCode should only end up non-nil if dontCanonicalizeAliases is passed to make_object(). -- props should either contain zero-argument functions to compute the value, or the value itself. local props = { ancestors = function() return self:getAncestorCodes() end, canonicalName = function() return self:getCanonicalName() end, categoryName = function() return self:getCategoryName("nocap") end, code = self._code, mainCode = self._mainCode, parent = function() return self:getParentCode() end, full = function() return self:getFullCode() end, stripDiacriticsPatterns = strip_diacritics_patterns, stripDiacriticsRemoveDiacritics = strip_diacritics_remove_diacritics, family = function() return self:getFamilyCode() end, aliases = function() return self:getAliases() end, varieties = function() return self:getVarieties() end, otherNames = function() return self:getOtherNames() end, pseudoFamilies = function() return self:getPseudoFamilies() end, scripts = function() return self:getScriptCodes() end, type = function() return keys_to_list(self:getTypes()) end, wikimediaLanguages = function() return self:getWikimediaLanguageCodes() end, wikidataItem = function() return self:getWikidataItem() end, wikipediaArticle = function() return self:getWikipediaArticle(true) end, } local ret = {} for prop, val in pairs(props) do if not opts.skip_fields or not opts.skip_fields[prop] then if type(val) == "function" then ret[prop] = val() else ret[prop] = val end end end -- Use `deep_copy` when returning a table, so that there are no editing restrictions imposed by `mw.loadData`. return opts and opts.lua_table and deep_copy(ret) or to_json(ret, opts) end function export.getDataModuleName(code) local letter = match(code, "^(%l)%l%l?$") return "Module:" .. ( letter == nil and "languages/data/exceptional" or #code == 2 and "languages/data/2" or "languages/data/3/" .. letter ) end get_data_module_name = export.getDataModuleName function export.getExtraDataModuleName(code) return get_data_module_name(code) .. "/extra" end get_extra_data_module_name = export.getExtraDataModuleName do local function make_stack(data) local key_types = { [2] = "unique", aliases = "unique", other_names = "unique", type = "append", varieties = "unique", wikipedia_article = "unique", wikimedia_codes = "unique" } local function __index(self, k) local stack, key_type = getmetatable(self), key_types[k] -- Data that isn't inherited from the parent. if key_type == "unique" then local v = stack[stack[make_stack]][k] if v == nil then local layer = stack[0] if layer then -- Could be false if there's no extra data. v = layer[k] end end return v -- Data that is appended by each generation. elseif key_type == "append" then local parts, offset, n = {}, 0, stack[make_stack] for i = 1, n do local part = stack[i][k] if part == nil then offset = offset + 1 else parts[i - offset] = part end end return offset ~= n and concat(parts, ",") or nil end local n = stack[make_stack] while true do local layer = stack[n] if not layer then -- Could be false if there's no extra data. return nil end local v = layer[k] if v ~= nil then return v end n = n - 1 end end local function __newindex() error("table is read-only") end local function __pairs(self) -- Iterate down the stack, caching keys to avoid duplicate returns. local stack, seen = getmetatable(self), {} local n = stack[make_stack] local iter, state, k, v = pairs(stack[n]) return function() repeat repeat k = iter(state, k) if k == nil then n = n - 1 local layer = stack[n] if not layer then -- Could be false if there's no extra data. return nil end iter, state, k = pairs(layer) end until not (k == nil or seen[k]) -- Get the value via a lookup, as the one returned by the -- iterator will be the raw value from the current layer, -- which may not be the one __index will return for that -- key. Also memoize the key in `seen` (even if the lookup -- returns nil) so that it doesn't get looked up again. -- TODO: store values in `self`, avoiding the need to create -- the `seen` table. The iterator will need to iterate over -- `self` with `next` first to find these on future loops. v, seen[k] = self[k], true until v ~= nil return k, v end end local __ipairs = require(table_module).indexIpairs function make_stack(data) local stack = { data, [make_stack] = 1, -- stores the length and acts as a sentinel to confirm a given metatable is a stack. __index = __index, __newindex = __newindex, __pairs = __pairs, __ipairs = __ipairs, } stack.__metatable = stack return setmetatable({}, stack), stack end return make_stack(data) end local function get_stack(data) local stack = getmetatable(data) return stack and type(stack) == "table" and stack[make_stack] and stack or nil end --[==[ <span style="color: var(--wikt-palette-red,#BA0000)">This function is not for use in entries or other content pages.</span> Return a blob of data about the language. The format of this blob is undocumented, and perhaps unstable; it's intended for things like the module's own unit-tests, which are "close friends" with the module and will be kept up-to-date as the format changes. If `extra` is set, any extra data in the relevant `/extra` module will be included. (Note that it will be included anyway if it has already been loaded into the language object.) If `raw` is set, then the returned data will not contain any data inherited from parent objects. -- Do NOT use these methods! -- All uses should be pre-approved on the talk page! ]==] function Language:getData(extra, raw) if extra then self:loadInExtraData() end local data = self._data -- If raw is not set, just return the data. if not raw then return data end local stack = get_stack(data) -- If there isn't a stack or its length is 1, return the data. Extra data (if any) will be included, as it's -- stored at key 0 and doesn't affect the reported length. if stack == nil then return data end local n = stack[make_stack] if n == 1 then return data end extra = stack[0] -- If there isn't any extra data, return the top layer of the stack. if extra == nil then return stack[n] end -- If there is, return a new stack which has the top layer at key 1 and the extra data at key 0. data, stack = make_stack(stack[n]) stack[0] = extra return data end function Language:loadInExtraData() -- Only full languages have extra data. if not self:hasType("language", "full") then return end local data = self._data -- If there's no stack, create one. local stack = get_stack(self._data) if stack == nil then data, stack = make_stack(data) -- If already loaded, return. elseif stack[0] ~= nil then return end self._data = data -- Load extra data from the relevant module and add it to the stack at key 0, so that the __index and __pairs -- metamethods will pick it up, since they iterate down the stack until they run out of layers. local code = self._code local modulename = get_extra_data_module_name(code) -- No data cached as false. stack[0] = modulename and load_data(modulename)[code] or false end --[==[ Return the name of the module containing the language's data, e.g. [[Module:languages/data/3/k]] for three-letter codes beginning with `k`. ]==] function Language:getDataModuleName() local name = self._dataModuleName if name == nil then name = self:hasType("etymology-only") and etymology_languages_data_module or get_data_module_name(self._mainCode or self._code) self._dataModuleName = name end return name end --[==[ Return the name of the module containing the language's extra data, e.g. [[Module:languages/data/3/k/extra]] for three-letter codes beginning with `k`. ]==] function Language:getExtraDataModuleName() local name = self._extraDataModuleName if name == nil then name = not self:hasType("etymology-only") and get_extra_data_module_name(self._mainCode or self._code) or false self._extraDataModuleName = name end return name or nil end function export.makeObject(code, data, dontCanonicalizeAliases) local data_type = type(data) if data_type ~= "table" then error(("bad argument #2 to 'makeObject' (table expected, got %s)"):format(data_type)) end -- Convert any aliases. local input_code = code code = normalize_code(code) input_code = dontCanonicalizeAliases and input_code or code local parent if data.parent then parent = get_by_code(data.parent, nil, true, true) else parent = Language end parent.__index = parent local lang = {_code = input_code} -- This can only happen if dontCanonicalizeAliases is passed to make_object(). if code ~= input_code then lang._mainCode = code end local parent_data = parent._data if parent_data == nil then -- Full code is the same as the code. lang._fullCode = parent._code or code else -- Copy full code. lang._fullCode = parent._fullCode local stack = get_stack(parent_data) if stack == nil then parent_data, stack = make_stack(parent_data) end -- Insert the input data as the new top layer of the stack. local n = stack[make_stack] + 1 data, stack[n], stack[make_stack] = parent_data, data, n end lang._data = data return setmetatable(lang, parent) end make_object = export.makeObject end --[==[ Find the language whose code matches the one provided. If it exists, return a `Language` object representing the language. Otherwise, return {nil}, unless `paramForError` is given, in which case an error is generated. If `paramForError` is {true}, a generic error message mentioning the bad code is generated; otherwise `paramForError` should be a string or number specifying the parameter that the code came from, and this parameter will be mentioned in the error message along with the bad code. If `allowEtymLang` is specified, etymology-only language codes are allowed and looked up along with full language codes. If `allowFamily` is specified, language family codes are allowed and looked up along with normal language codes. ]==] function export.getByCode(code, paramForError, allowEtymLang, allowFamily) -- [[ Track uses of paramForError, ultimately so it can be removed, as error-handling should be done by -- [[Module:parameters]], not here. ]] -- I (Benwing) disagree; that is an idealistic goal unlikely to be achievable -- realistically. if paramForError ~= nil then track("paramForError") end if type(code) ~= "string" then local typ if not code then typ = "nil" elseif check_object("language", true, code) then typ = "a language object" elseif check_object("family", true, code) then typ = "a family object" else typ = "a " .. type(code) end error("The function getByCode expects a string as its first argument, but received " .. typ .. ".") end local m_data = load_data(languages_data_module) if m_data.aliases[code] or m_data.track[code] then track(code) end local norm_code = normalize_code(code) -- Get the data, checking for etymology-only languages if allowEtymLang is set. local data = load_data(get_data_module_name(norm_code))[norm_code] or allowEtymLang and load_data(etymology_languages_data_module)[norm_code] -- If no data was found and allowFamily is set, check the family data. If the main family data was found, make the -- object with [[Module:families]] instead, as family objects have different methods. However, if it's an -- etymology-only family, use make_object in this module (which handles object inheritance), and the -- family-specific methods will be inherited from the parent object. if data == nil and allowFamily then data = load_data("Module:families/data")[norm_code] if data ~= nil then if data.parent == nil then return make_family_object(norm_code, data) elseif not allowEtymLang then data = nil end end end local retval = code and data and make_object(code, data) if not retval and paramForError then require("Module:languages/errorGetBy").code(code, paramForError, allowEtymLang, allowFamily) end return retval end get_by_code = export.getByCode --[==[ Find the language whose canonical name (the name used to represent that language on Wiktionary) or other name matches the one provided. If it exists, return a `Language` object representing the language. Otherwise, return {nil}, unless `paramForError` is given, in which case an error is generated. If `allowEtymLang` is specified, etymology-only language codes are allowed and looked up along with full language codes. If `allowFamily` is specified, language family codes are allowed and looked up along with normal language codes. The canonical name of languages should always be unique (it is an error for two languages on Wiktionary to share the same canonical name), so this is guaranteed to give at most one result. This function is powered by [[Module:languages/canonical names]], which contains a pre-generated mapping of full-language canonical names to codes. It is generated by going through the [[:Category:Language data modules]] for full languages. When `allowEtymLang` is specified for the above function, [[Module:etymology languages/canonical names]] may also be used, and when `allowFamily` is specified for the above function, [[Module:families/canonical names]] may also be used. ]==] function export.getByCanonicalName(name, errorIfInvalid, allowEtymLang, allowFamily) local byName = load_data("Module:languages/canonical names") local code = byName and byName[name] if not code and allowEtymLang then byName = load_data("Module:etymology languages/canonical names") code = byName and byName[name] or byName[gsub(name, " [Ss]ubstrate$", "")] or byName[gsub(name, "^a ", "")] or byName[gsub(name, "^a ", ""):gsub(" [Ss]ubstrate$", "")] or -- For etymology families like "ira-pro". -- FIXME: This is not ideal, as it allows " languages" to be appended to any etymology-only language, too. byName[match(name, "^(.*) languages$")] end if not code and allowFamily then byName = load_data("Module:families/canonical names") code = byName[name] or byName[match(name, "^(.*) languages$")] end local retval = code and get_by_code(code, errorIfInvalid, allowEtymLang, allowFamily) if not retval and errorIfInvalid then require("Module:languages/errorGetBy").canonicalName(name, allowEtymLang, allowFamily) end return retval end --[==[ Used by [[Module:languages/data/2]] (et al.) and [[Module:etymology languages/data]], [[Module:families/data]], [[Module:scripts/data]] and [[Module:writing systems/data]] to finalize the data into the format that is actually returned. ]==] function export.finalizeData(data, main_type, variety) local fields = {"type"} if main_type == "language" then insert(fields, 4) -- script codes insert(fields, "ancestors") insert(fields, "link_tr") insert(fields, "override_translit") insert(fields, "wikimedia_codes") elseif main_type == "script" then insert(fields, 3) -- writing system codes end -- Families and writing systems have no extra fields to process. local fields_len = #fields for _, entity in next, data do if variety then -- Move parent from 3 to "parent" and family from "family" to 3. These are different for the sake of -- convenience, since very few varieties have the family specified, whereas all of them have a parent. entity.parent, entity[3] = entity[3], entity.family entity.family = nil -- Give the type "regular" iff not a variety and no other types are assigned. elseif not (entity.type or entity.parent) then entity.type = "regular" end for i = 1, fields_len do local key = fields[i] local field = entity[key] if field and type(field) == "string" then entity[key] = gsub(field, "%s*,%s*", ",") end end end return data end --[==[ For backwards compatibility only; modules should require the error themselves. ]==] function export.err(lang_code, param, code_desc, template_tag, not_real_lang) return require("Module:languages/error")(lang_code, param, code_desc, template_tag, not_real_lang) end return export 4xxf6ciorqun7es6a4t85giu2wzcz7w 393949 393946 2026-10-08T19:33:08Z Ow! That Hurts! 8952 393949 Scribunto text/plain --[==[ intro: This module implements fetching of language-specific information and processing text in a given language. ===Types of languages=== There are two types of languages: full languages and etymology-only languages. The essential difference is that only full languages appear in L2 headings in vocabulary entries, and hence categories like [[:Category:French nouns]] exist only for full languages. Etymology-only languages have either a full language or another etymology-only language as their parent (in the parent-child inheritance sense), and for etymology-only languages with another etymology-only language as their parent, a full language can always be derived by following the parent links upwards. For example, "Canadian French", code `fr-CA`, is an etymology-only language whose parent is the full language "French", code `fr`. An example of an etymology-only language with another etymology-only parent is "Northumbrian Old English", code `ang-nor`, which has "Anglian Old English", code `ang-ang` as its parent; this is an etymology-only language whose parent is "Old English", code `ang`, which is a full language. (This is because Northumbrian Old English is considered a variety of Anglian Old English.) Sometimes the parent is the "Undetermined" language, code `und`; this is the case, for example, for "substrate" languages such as "Pre-Greek", code `qsb-grc`, and "the BMAC substrate", code `qsb-bma`. It is important to distinguish language ''parents'' from language ''ancestors''. The parent-child relationship is one of containment, i.e. if X is a child of Y, X is considered a variety of Y. On the other hand, the ancestor-descendant relationship is one of descent in time. For example, "Classical Latin", code `la-cla`, and "Late Latin", code `la-lat`, are both etymology-only languages with "Latin", code `la`, as their parents, because both of the former are varieties of Latin. However, Late Latin does *NOT* have Classical Latin as its parent because Late Latin is *not* a variety of Classical Latin; rather, it is a descendant. There is in fact a separate `ancestors` field that is used to express the ancestor-descendant relationship, and Late Latin's ancestor is given as Classical Latin. It is also important to note that sometimes an etymology-only language is actually the conceptual ancestor of its parent language. This happens, for example, with "Old Italian" (code `roa-oit`), which is an etymology-only variant of full language "Italian" (code `it`), and with "Old Latin" (code `itc-ola`), which is an etymology-only variant of Latin. In both cases, the full language has the etymology-only variant listed as an ancestor. This allows a Latin term to inherit from Old Latin using the {{tl|inh}} template (where in this template, "inheritance" refers to ancestral inheritance, i.e. inheritance in time, rather than in the parent-child sense); likewise for Italian and Old Italian. Full languages come in three subtypes: * {regular}: This indicates a full language that is attested according to [[WT:CFI]] and therefore permitted in the main namespace. There may also be reconstructed terms for the language, which are placed in the {Reconstruction} namespace and must be prefixed with * to indicate a reconstruction. Most full languages are natural (not constructed) languages, but a few constructed languages (e.g. Esperanto and Volapük, among others) are also allowed in the mainspace and considered regular languages. * {reconstructed}: This language is not attested according to [[WT:CFI]], and therefore is allowed only in the {Reconstruction} namespace. All terms in this language are reconstructed, and must be prefixed with *. Languages such as Proto-Indo-European and Proto-Germanic are in this category. * {appendix-constructed}: This language is attested but does not meet the additional requirements set out for constructed languages ([[WT:CFI#Constructed languages]]). Its entries must therefore be in the Appendix namespace, but they are not reconstructed and therefore should not have * prefixed in links. Most constructed languages are of this subtype. Both full languages and etymology-only languages have a {Language} object associated with them, which is fetched using the {getByCode} function in [[Module:languages]] to convert a language code to a {Language} object. Depending on the options supplied to this function, etymology-only languages may or may not be accepted, and family codes may be accepted (returning a {Family} object as described in [[Module:families]]). There are also separate {getByCanonicalName} functions in [[Module:languages]] and [[Module:etymology languages]] to convert a language's canonical name to a {Language} object (depending on whether the canonical name refers to a full or etymology-only language). ===Textual representations=== Textual strings belonging to a given language come in several different ''text variants'': # The ''input text'' is what the user supplies in wikitext, in the parameters to {{tl|m}}, {{tl|l}}, {{tl|ux}}, {{tl|t}}, {{tl|lang}} and the like. # The ''corrected input text'' is the input text with some corrections and/or normalizations applied, such as bad-character replacements for certain languages, like replacing `l` or `1` to [[palochka]] in some languages written in Cyrillic. (FIXME: This currently goes under the name ''display text'' but that will be repurposed below. Also, [[User:Surjection]] suggests renaming this to ''normalized input text'', but "normalized" is used in a different sense in [[Module:usex]].) # The ''display text'' is the text in the form as it will be displayed to the user. This is what appears in headwords, in usexes, in displayed internal links, etc. This can include accent marks that are removed to form the stripped display text (see below), as well as embedded bracketed links that are variously processed further. The display text is generated from the corrected input text by applying language-specific transformations; for most languages, there will be no such transformations. The general reason for having a difference between input and display text is to allow for extra information in the input text that is not displayed to the user but is sent to the transliteration module. Note that having different display and input text is only supported currently through special-casing but will be generalized. Examples of transformations are: (1) Removing the {{cd|^}} that is used in certain East Asian (and possibly other unicameral) languages to indicate capitalization of the transliteration (which is currently special-cased); (2) for Korean, removing or otherwise processing hyphens (which is currently special-cased); (3) for Arabic, removing a ''sukūn'' diacritic placed over a ''tāʔ marbūṭa'' (like this: ةْ) to indicate that the ''tāʔ marbūṭa'' is pronounced and transliterated as /t/ instead of being silent [NOTE, NOT IMPLEMENTED YET]; (4) for Thai and Khmer, converting space-separated words to bracketed words and resolving respelling substitutions such as `[กรีน/กฺรีน]`, which indicate how to transliterate given words [NOTE, NOT IMPLEMENTED YET except in language-specific templates like {{tl|th-usex}}]. ## The ''right-resolved display text'' is the result of removing brackets around one-part embedded links and resolving two-part embedded links into their right-hand components (i.e. converting two-part links into the displayed form). The process of right-resolution is what happens when you call {{cd|remove_links()}} in [[Module:links]] on some text. When applied to the display text, it produces exactly what the user sees, without any link markup. # The ''stripped display text'' is the result of applying diacritic-stripping to the display text. ## The ''left-resolved stripped display text'' [NEED BETTER NAME] is the result of applying left-resolution to the stripped display text, i.e. similar to right-resolution but resolving two-part embedded links into their left-hand components (i.e. the linked-to page). If the display text refers to a single page, the resulting of applying diacritic stripping and left-resolution produces the ''logical pagename''. # The ''physical pagename text'' is the result of converting the stripped display text into physical page links. If the stripped display text contains embedded links, the left side of those links is converted into physical page links; otherwise, the entire text is considered a pagename and converted in the same fashion. The conversion does three things: (1) converts characters not allowed in pagenames into their "unsupported title" representation, e.g. {{cd|Unsupported titles/`gt`}} in place of the logical name {{cd|>}}; (2) handles certain special-cased unsupported-title logical pagenames, such as {{cd|Unsupported titles/Space}} in place of {{cd|[space]}} and {{cd|Unsupported titles/Ancient Greek dish}} in place of a very long Greek name for a gourmet dish as found in Aristophanes; (3) converts "mammoth" pagenames such as [[a]] into their appropriate split component, e.g. [[a/languages A to L]]. # The ''source translit text'' is the text as supplied to the language-specific {{cd|transliterate()}} method. The form of the source translit text may need to be language-specific, e.g Thai and Khmer will need the corrected input text, whereas other languages may need to work off the display text. [FIXME: It's still unclear to me how embedded bracketed links are handled in the existing code.] In general, embedded links need to be right-resolved (see above), but when this happens is unclear to me [FIXME]. Some languages have a chop-up-and-paste-together scheme that sends parts of the text through the transliterate mechanism, and for others (those listed with "cont" in {{cd|substitution}} in [[Module:languages/data]]) they receive the full input text, but preprocessed in certain ways. (The wisdom of this is still unclear to me.) # The ''transliterated text'' (or ''transliteration'') is the result of transliterating the source translit text. Unlike for all the other text variants except the transcribed text, it is always in the Latin script. # The ''transcribed text'' (or ''transcription'') is the result of transcribing the source translit text, where "transcription" here means a close approximation to the phonetic form of the language in languages (e.g. Akkadian, Sumerian, Ancient Egyptian, maybe Tibetan) that have a wide difference between the written letters and spoken form. Unlike for all the other text variants other than the transliterated text, it is always in the Latin script. Currently, the transcribed text is always supplied manually be the user; there is no such thing as a {{cd|transcribe()}} method on language objects. # The ''sort key'' is the text used in sort keys for determining the placing of pages in categories they belong to. The sort key is generated from the pagename or a specified ''sort base'' by lowercasing, doing language-specific transformations and then uppercasing the result. If the sort base is supplied and is generated from input text, it needs to be converted to display text, have embedded links removed through right-resolution and have diacritic-stripping applied. # There are other text variants that occur in usexes (specifically, there are normalized variants of several of the above text variants), but we can skip them for now. The following methods exist on {Language} objects to convert between different text variants: # {correctInputText} (currently called {makeDisplayText}): This converts input text to corrected input text. # {stripDiacritics}: This converts to stripped display text. [FIXME: This needs some rethinking. In particular, {stripDiacritics} is sometimes called on input text, corrected input text or display text (in various paths inside of [[Module:links]], and, in the case of input text, usually from other modules). We need to make sure we don't try to convert input text to display text twice, but at the same time we need to support calling it directly on input text since so many modules do this. This means we need to add a parameter indicating whether the passed-in text is input, corrected input, or display text; if the former two, we call {correctInputText} ourselves.] # {logicalToPhysical}: This converts logical pagenames to physical pagenames. # {transliterate}: This appears to convert input text with embedded brackets removed into a transliteration. [FIXME: This needs some rethinking. In particular, it calls {processDisplayText} on its input, which won't work for Thai and Khmer, so we may need language-specific flags indicating whether to pass the input text directly to the language transliterate method. In addition, I'm not sure how embedded links are handled in the existing translit code; a lot of callers remove the links themselves before calling {transliterate()}, which I assume is wrong.] # {makeSortKey}: This converts display text (?) to a sort key. [FIXME: Clarify this.] ]==] local export = {} local debug_track_module = "Module:debug/track" local etymology_languages_data_module = "Module:etymology languages/data" local families_module = "Module:families" local headword_page_module = "Module:headword/page" local json_module = "Module:JSON" local language_like_module = "Module:language-like" local languages_data_module = "Module:tungumál/gögn" local languages_data_patterns_module = "Module:languages/data/patterns" local links_data_module = "Module:links/data" local load_module = "Module:load" local scripts_module = "Module:scripts" local scripts_data_module = "Module:scripts/data" local string_encode_entities_module = "Module:string/encode entities" local string_pattern_escape_module = "Module:string/patternEscape" local string_replacement_escape_module = "Module:string/replacementEscape" local string_utilities_module = "Module:string utilities" local table_module = "Module:table" local utilities_module = "Module:utilities" local wikimedia_languages_module = "Module:wikimedia languages" local mw = mw local string = string local table = table local char = string.char local concat = table.concat local find = string.find local floor = math.floor local get_by_code -- Defined below. local get_data_module_name -- Defined below. local get_extra_data_module_name -- Defined below. local getmetatable = getmetatable local gmatch = string.gmatch local gsub = string.gsub local insert = table.insert local ipairs = ipairs local is_known_language_tag = mw.language.isKnownLanguageTag local make_object -- Defined below. local match = string.match local next = next local pairs = pairs local remove = table.remove local require = require local select = select local setmetatable = setmetatable local sub = string.sub local type = type local unstrip = mw.text.unstrip -- Loaded as needed by findBestScript. local Hans_chars local Hant_chars local function check_object(...) check_object = require(utilities_module).check_object return check_object(...) end local function debug_track(...) debug_track = require(debug_track_module) return debug_track(...) end local function decode_entities(...) decode_entities = require(string_utilities_module).decode_entities return decode_entities(...) end local function decode_uri(...) decode_uri = require(string_utilities_module).decode_uri return decode_uri(...) end local function deep_copy(...) deep_copy = require(table_module).deepCopy return deep_copy(...) end local function encode_entities(...) encode_entities = require(string_encode_entities_module) return encode_entities(...) end local function get_L2_sort_key(...) get_L2_sort_key = require(headword_page_module).get_L2_sort_key return get_L2_sort_key(...) end local function get_script(...) get_script = require(scripts_module).getByCode return get_script(...) end local function find_best_script_without_lang(...) find_best_script_without_lang = require(scripts_module).findBestScriptWithoutLang return find_best_script_without_lang(...) end local function get_family(...) get_family = require(families_module).getByCode return get_family(...) end local function get_plaintext(...) get_plaintext = require(utilities_module).get_plaintext return get_plaintext(...) end local function get_wikimedia_lang(...) get_wikimedia_lang = require(wikimedia_languages_module).getByCode return get_wikimedia_lang(...) end local function keys_to_list(...) keys_to_list = require(table_module).keysToList return keys_to_list(...) end local function list_to_set(...) list_to_set = require(table_module).listToSet return list_to_set(...) end local function load_data(...) load_data = require(load_module).load_data return load_data(...) end local function make_family_object(...) make_family_object = require(families_module).makeObject return make_family_object(...) end local function pattern_escape(...) pattern_escape = require(string_pattern_escape_module) return pattern_escape(...) end local function replacement_escape(...) replacement_escape = require(string_replacement_escape_module) return replacement_escape(...) end local function safe_require(...) safe_require = require(load_module).safe_require return safe_require(...) end local function shallow_copy(...) shallow_copy = require(table_module).shallowCopy return shallow_copy(...) end local function split(...) split = require(string_utilities_module).split return split(...) end local function to_json(...) to_json = require(json_module).toJSON return to_json(...) end local function u(...) u = require(string_utilities_module).char return u(...) end local function ugsub(...) ugsub = require(string_utilities_module).gsub return ugsub(...) end local function ulen(...) ulen = require(string_utilities_module).len return ulen(...) end local function ulower(...) ulower = require(string_utilities_module).lower return ulower(...) end local function umatch(...) umatch = require(string_utilities_module).match return umatch(...) end local function uupper(...) uupper = require(string_utilities_module).upper return uupper(...) end local function track(page) debug_track("languages/" .. page) return true end local function normalize_code(code) return load_data(languages_data_module).aliases[code] or code end local function check_inputs(self, check, default, ...) local n = select("#", ...) if n == 0 then return false end local ret = check(self, (...)) if ret ~= nil then return ret elseif n > 1 then local inputs = {...} for i = 2, n do ret = check(self, inputs[i]) if ret ~= nil then return ret end end end return default end local function make_link(self, target, display) local prefix, main if self:getFamilyCode() == "qfa-sub" then prefix, main = display:match("^(the )(.*)") if not prefix then prefix, main = display:match("^(a )(.*)") end end return (prefix or "") .. "[[" .. target .. "|" .. (main or display) .. "]]" end -- Convert risky characters to HTML entities, which minimizes interference once returned (e.g. for "sms:a", "<!-- -->" etc.). local function escape_risky_characters(text) -- Spacing characters in isolation generally need to be escaped in order to be properly processed by the MediaWiki -- software. if umatch(text, "^%s*$") then return encode_entities(text, text) end return encode_entities(text, "!#%&*+/:;<=>?@[\\]_{|}") end -- Temporarily convert various formatting characters to PUA to prevent them from being disrupted by the substitution process. local function doTempSubstitutions(text, subbedChars, keepCarets, noTrim) -- Clone so that we don't insert any extra patterns into the table in package.loaded. For some reason, using require -- seems to keep memory use down; probably because the table is always cloned. local patterns = shallow_copy(require(languages_data_patterns_module)) if keepCarets then insert(patterns, "((\\+)%^)") insert(patterns, "((%^))") end -- Ensure any whitespace at the beginning and end is temp substituted, to prevent it from being accidentally -- trimmed. We only want to trim any final spaces added during the substitution process (e.g. by a module), which -- means we only do this during the first round of temp substitutions. if not noTrim then insert(patterns, "^([\128-\191\244]*(%s+))") insert(patterns, "((%s+)[\128-\191\244]*)$") end -- Pre-substitution, of "[[" and "]]", which makes pattern matching more accurate. text = gsub(text, "%f[%[]%[%[", "\1"):gsub("%f[%]]%]%]", "\2") local i = #subbedChars for _, pattern in ipairs(patterns) do -- Patterns ending in \0 stand are for things like "[[" or "]]"), so the inserted PUA are treated as breaks -- between terms by modules that scrape info from pages. local term_divider pattern = gsub(pattern, "%z$", function(divider) term_divider = divider == "\0" return "" end) text = gsub(text, pattern, function(...) local m = {...} local m1New = m[1] for k = 2, #m do local n = i + k - 1 subbedChars[n] = m[k] local byte2 = floor(n / 4096) % 64 + (term_divider and 128 or 136) local byte3 = floor(n / 64) % 64 + 128 local byte4 = n % 64 + 128 m1New = gsub(m1New, pattern_escape(m[k]), "\244" .. char(byte2) .. char(byte3) .. char(byte4), 1) end i = i + #m - 1 return m1New end) end text = gsub(text, "\1", "%[%["):gsub("\2", "%]%]") return text, subbedChars end -- Reinsert any formatting that was temporarily substituted. local function undoTempSubstitutions(text, subbedChars) for i = 1, #subbedChars do local byte2 = floor(i / 4096) % 64 + 128 local byte3 = floor(i / 64) % 64 + 128 local byte4 = i % 64 + 128 text = gsub(text, "\244[" .. char(byte2) .. char(byte2+8) .. "]" .. char(byte3) .. char(byte4), replacement_escape(subbedChars[i])) end text = gsub(text, "\1", "%[%["):gsub("\2", "%]%]") return text end -- Check if the raw text is an unsupported title, and if so return that. Otherwise, remove HTML entities. We do the -- pre-conversion to avoid loading the unsupported title list unnecessarily. local function checkNoEntities(text) local textNoEnc = decode_entities(text) if textNoEnc ~= text and load_data(links_data_module).unsupported_titles[text] then return text else return textNoEnc end end -- If no script object is provided (or if it's invalid or None), get one. local function checkScript(text, self, sc) if not check_object("script", true, sc) or sc:getCode() == "None" then return self:findBestScript(text) end return sc end local function normalize(text, sc) text = sc:fixDiscouragedSequences(text) return sc:toFixedNFD(text) end --[=[ Subfunction of iterateSectionSubstitutions(). Process an individual chunk of text according to the specifications in `substitution_data`. The input parameters are all as in the documentation of iterateSectionSubstitutions() except for `recursed`, which is set to true if we called ourselves recursively to process a script-specific setting or script-wide fallback. Returns two values: the processed text and the actual substitution data used to do the substitutions (same as the `actual_substitution_data` return value to iterateSectionSubstitutions()). ]=] local function doSubstitutions(self, text, sc, substitution_data, data_field, function_name, recursed) -- BE CAREFUL in this function because the value at any level can be `false`, which causes no processing to be done -- and blocks any further fallback processing. local actual_substitution_data = substitution_data -- If there are language-specific substitutes given in the data module, use those. if type(substitution_data) == "table" then -- If a script is specified, run this function with the script-specific data before continuing. local sc_code = sc:getCode() local has_substitution_data = false if substitution_data[sc_code] ~= nil then has_substitution_data = true if substitution_data[sc_code] then text, actual_substitution_data = doSubstitutions(self, text, sc, substitution_data[sc_code], data_field, function_name, true) end -- Hant, Hans and Hani are usually treated the same, so add a special case to avoid having to specify each one -- separately. elseif sc_code:match("^Han") and substitution_data.Hani ~= nil then has_substitution_data = true if substitution_data.Hani then text, actual_substitution_data = doSubstitutions(self, text, sc, substitution_data.Hani, data_field, function_name, true) end -- Substitution data with key 1 in the outer table may be given as a fallback. elseif substitution_data[1] ~= nil then has_substitution_data = true if substitution_data[1] then text, actual_substitution_data = doSubstitutions(self, text, sc, substitution_data[1], data_field, function_name, true) end end -- Iterate over all strings in the "from" subtable, and gsub with the corresponding string in "to". We work with -- the NFD decomposed forms, as this simplifies many substitutions. if substitution_data.from then has_substitution_data = true for i, from in ipairs(substitution_data.from) do -- Normalize each loop, to ensure multi-stage substitutions work correctly. text = sc:toFixedNFD(text) text = ugsub(text, sc:toFixedNFD(from), substitution_data.to[i] or "") end end if substitution_data.remove_diacritics then has_substitution_data = true text = sc:toFixedNFD(text) -- Convert exceptions to PUA. local remove_exceptions, substitutes = substitution_data.remove_exceptions if remove_exceptions then substitutes = {} local i = 0 for _, exception in ipairs(remove_exceptions) do exception = sc:toFixedNFD(exception) text = ugsub(text, exception, function(m) i = i + 1 local subst = u(0x80000 + i) substitutes[subst] = m return subst end) end end -- Strip diacritics. text = ugsub(text, "[" .. substitution_data.remove_diacritics .. "]", "") -- Convert exceptions back. if remove_exceptions then text = text:gsub("\242[\128-\191]*", substitutes) end end if not has_substitution_data and sc._data[data_field] then -- If language-specific sort key (etc.) is nil, fall back to script-wide sort key (etc.). text, actual_substitution_data = doSubstitutions(self, text, sc, sc._data[data_field], data_field, function_name, true) end elseif type(substitution_data) == "string" then -- If there is a dedicated function module, use that. local module = safe_require("Module:" .. substitution_data) if module then -- TODO: translit functions should take objects, not codes. -- TODO: translit functions should be called with form NFD. if function_name == "tr" then if not module[function_name] then error(("Internal error: Module [[%s]] has no function named 'tr'"):format(substitution_data)) end text = module[function_name](text, self._code, sc:getCode()) elseif function_name == "stripDiacritics" then -- FIXME, get rid of this arm after renaming makeEntryName -> stripDiacritics. if module[function_name] then text = module[function_name](sc:toFixedNFD(text), self, sc) elseif module.makeEntryName then text = module.makeEntryName(sc:toFixedNFD(text), self, sc) else error(("Internal error: Module [[%s]] has no function named 'stripDiacritics' or 'makeEntryName'" ):format(substitution_data)) end else if not module[function_name] then error(("Internal error: Module [[%s]] has no function named '%s'"):format( substitution_data, function_name)) end text = module[function_name](sc:toFixedNFD(text), self, sc) end else error("Substitution data '" .. substitution_data .. "' does not match an existing module.") end elseif substitution_data == nil and sc._data[data_field] then -- If language-specific sort key (etc.) is nil, fall back to script-wide sort key (etc.). text, actual_substitution_data = doSubstitutions(self, text, sc, sc._data[data_field], data_field, function_name, true) end -- Don't normalize to NFC if this is the inner loop or if a module returned nil. if recursed or not text then return text, actual_substitution_data end -- Fix any discouraged sequences created during the substitution process, and normalize into the final form. return sc:toFixedNFC(sc:fixDiscouragedSequences(text)), actual_substitution_data end --[=[ Split the text into sections, based on the presence of temporarily substituted formatting characters, then iterate over each section to apply substitutions (e.g. transliteration or diacritic stripping). This avoids putting PUA (Private Use Area) characters through language-specific modules, which may be unequipped for them. This function is passed the following values: * `self` (the Language object); * `text` (the text to process); * `sc` (the script of the text, which must be specified; callers should call checkScript() as needed to autodetect the script of the text if not given explicitly by the user); * `subbedChars` (an array of the same length as the text, indicating which characters have been substituted and by what, or {nil} if no substitutions are to happen); * `keepCarets` (DOCUMENT ME); * `substitution_data` (the data indicating which substitutions to apply, taken directly from `data_field` in the language's data structure in a submodule of [[Module:languages/data]]); * `data_field` (the field from which `substitution_data` was fetched, such as {"sort_key"} or {"strip_diacritics"}); * `function_name` (the name of the function to call to do the substitution, in case `substitution_data` specifies a module to do the substitution); * `notrim` (don't trim whitespace at the edges of `text`; set when computing the sort key, because whitespace at the beginning of a sort key is significant and causes the resulting page to be sorted at the beginning of the category it's in). Return three values: # the processed text; # the value of `subbedChars` that was passed in, possibly modified with additional character substitutions; will be {nil} if {nil} was passed in; # the actual substitution data that was used to apply substitutions to `text`; this may be different from the value of `substitution_data` passed in if that value recursively specified script-specific substitutions or if no substitution data could be found in the language-specific data (e.g. {nil} was passed in or a structure was passed in that had no setting for the script given in `sc`), but a script-wide fallback value was set; currently it is only used by {makeSortKey()}. ]=] local function iterateSectionSubstitutions(self, text, sc, subbedChars, keepCarets, substitution_data, data_field, function_name, notrim) local sections -- See [[Module:languages/data]]. if not find(text, "\244") or load_data(languages_data_module).substitution[self._code] == "cont" then sections = {text} else sections = split(text, "\244[\128-\143][\128-\191]*", true) end local actual_substitution_data for _, section in ipairs(sections) do -- Don't bother processing empty strings or whitespace (which may also not be handled well by dedicated -- modules). if gsub(section, "%s+", "") ~= "" then local sub, this_actual_substitution_data = doSubstitutions(self, section, sc, substitution_data, data_field, function_name) actual_substitution_data = this_actual_substitution_data -- Second round of temporary substitutions, in case any formatting was added by the main substitution -- process. However, don't do this if the section contains formatting already (as it would have had to have -- been escaped to reach this stage, and therefore should be given as raw text). if sub and subbedChars then local noSub for _, pattern in ipairs(require(languages_data_patterns_module)) do if match(section, pattern .. "%z?") then noSub = true end end if not noSub then sub, subbedChars = doTempSubstitutions(sub, subbedChars, keepCarets, true) end end if not sub then text = sub break end text = sub and gsub(text, pattern_escape(section), replacement_escape(sub), 1) or text end end if not notrim then -- Trim, unless there are only spacing characters, while ignoring any final formatting characters. -- Do not trim sort keys because spaces at the beginning are significant. text = text and text:gsub("^([\128-\191\244]*)%s+(%S)", "%1%2"):gsub("(%S)%s+([\128-\191\244]*)$", "%1%2") or nil end return text, subbedChars, actual_substitution_data end -- Process carets (and any escapes). Default to simple removal, if no pattern/replacement is given. local function processCarets(text, pattern, repl) local rep repeat text, rep = gsub(text, "\\\\(\\*^)", "\3%1") until rep == 0 return (text:gsub("\\^", "\4") :gsub(pattern or "%^", repl or "") :gsub("\3", "\\") :gsub("\4", "^")) end local function hasUnescapedCarets(text) return processCarets(text, "%^", "\1"):find("\1", 1, true) ~= nil end local function assertCaretCapitalizationAllowed(text, sc) if sc:forbidsCaretCapitalization() and hasUnescapedCarets(text) then error(("Caret (^) cannot be used to capitalize transliterations for the %s script " .. "(see caret_capitalization in [[Module:scripts/data]]).") :format(sc:getCanonicalName()), 2) end end -- Remove carets if they are used to capitalize parts of transliterations (unless they have been escaped). local function removeCarets(text, sc) if text:find("^", 1, true) then assertCaretCapitalizationAllowed(text, sc) if sc:hasCaretCapitalization() then return processCarets(text) end end return text end local Language = {} --[==[ Return the language code of the language. Example: {fr"} for French. ]==] function Language:getCode() return self._code end --[==[ Return the canonical name of the language. This is the name used to represent that language on Wiktionary, and is guaranteed to be unique to that language alone. Example: {"French"} for French. ]==] function Language:getCanonicalName() local name = self._name if name == nil then name = self._data[1] self._name = name end return name end --[==[ Return the display form of the language. The display form of a language, family or script is the form it takes when appearing as the <code><var>source</var></code> in categories such as <code>English terms derived from <var>source</var></code> or <code>English given names from <var>source</var></code>, and is also the displayed text in `##makeCategoryLink()` links. For full and etymology-only languages, this is the same as the canonical name, but for families, it reads <code>"<var>name</var> languages"</code> (e.g. {"Indo-Iranian languages"}), and for scripts, it reads <code>"<var>name</var> script"</code> (e.g. {"Arabic script"}). ]==] function Language:getDisplayForm() local form = self._displayForm if form == nil then form = self:getCanonicalName() -- Add article and " substrate" to substrates that lack them. if self:getFamilyCode() == "qfa-sub" then if not (sub(form, 1, 4) == "the " or sub(form, 1, 2) == "a ") then form = "a " .. form end if not match(form, " [Ss]ubstrate") then form = form .. " substrate" end end self._displayForm = form end return form end --[==[ Return the value which should be used in the HTML `lang=` attribute for tagged text in the language. ]==] function Language:getHTMLAttribute(sc, region) local code = self._code if not find(code, "-", 1, true) then return code .. "-" .. sc:getCode() .. (region and "-" .. region or "") end local parent = self:getParent() region = region or match(code, "%f[%u][%u-]+%f[%U]") if parent then return parent:getHTMLAttribute(sc, region) end -- TODO: ISO family codes can also be used. return "mis-" .. sc:getCode() .. (region and "-" .. region or "") end --[==[ Return a list of the aliases that the language is known by, excluding the canonical name. Aliases are synonyms for the language in question. The names are not guaranteed to be unique, in that sometimes more than one language is known by the same name. Example: { {"High German", "New High German", "Deutsch"}} for {{lnl|de}}. ]==] function Language:getAliases() self:loadInExtraData() return require(language_like_module).getAliases(self) end --[==[ Return a list of the known subvarieties of a given language, excluding subvarieties that have been given explicit etymology-only language codes. The names are not guaranteed to be unique, in that sometimes a given name refers to a subvariety of more than one language. Example: { {"Southern Aymara", "Central Aymara"}} for {{lnl|ay}}. Note that the returned value can have nested tables in it, when a subvariety goes by more than one name. Example: { {"North Azerbaijani", "South Azerbaijani", {"Afshar", "Afshari", "Afshar Azerbaijani", "Afchar"}, {"Qashqa'i", "Qashqai", "Kashkay"}, "Sonqor"}} for {{lnl|az}}. Here, for example, Afshar, Afshari, Afshar Azerbaijani and Afchar all refer to the same subvariety, whose preferred name is Afshar (the one listed first). To avoid a return value with nested tables in it, specify a non-{nil} value for the `flatten` parameter; in that case, the return value would be { {"North Azerbaijani", "South Azerbaijani", "Afshar", "Afshari", "Afshar Azerbaijani", "Afchar", "Qashqa'i", "Qashqai", "Kashkay", "Sonqor"}}. ]==] function Language:getVarieties(flatten) self:loadInExtraData() return require(language_like_module).getVarieties(self, flatten) end --[==[ Return a table of the "other names" that the language is known by, which are listed in the `other_names` field in the language's extra-data module. It should be noted that the `other_names` field itself is deprecated, and entries listed there should eventually be moved to either `aliases` or `varieties`, or removed if they refer to larger entities which the language in question is actually a part of. ]==] function Language:getOtherNames() -- To be eventually removed, once there are no more uses of the `other_names` field. self:loadInExtraData() return require(language_like_module).getOtherNames(self) end --[==[ Return a combined table of the canonical name, aliases, varieties and other names of a given language. ]==] function Language:getAllNames() self:loadInExtraData() return require(language_like_module).getAllNames(self) end --[==[ Return a table of types as a set (with the types as keys). The possible types are * {language}: This is a language, either full or etymology-only. * {full}: This is a "full" (not etymology-only) language, i.e. the union of {regular}, {reconstructed} and {appendix-constructed}. Note that the types {full} and {etymology-only} also exist for families, so if you want to check specifically for a full language and you have an object that might be a family, you should use {hasType("language", "full")} and not simply {hasType("full")}. * {etymology-only}: This is an etymology-only (not full) language, whose parent is another etymology-only language or a full language. Note that the types {full} and {etymology-only} also exist for families, so if you want to check specifically for an etymology-only language and you have an object that might be a family, you should use {hasType("language", "etymology-only")} and not simply {hasType("etymology-only")}. * {regular}: This indicates a full language that is attested according to [[WT:CFI]] and therefore permitted in the main namespace. There may also be reconstructed terms for the language, which are placed in the {Reconstruction} namespace and must be prefixed with `*` to indicate a reconstruction. Most full languages are natural (not constructed) languages, but a few constructed languages (e.g. Esperanto and Volapük, among others) are also allowed in the mainspace and considered regular languages. * {reconstructed}: This language is not attested according to [[WT:CFI]], and therefore is allowed only in the {Reconstruction} namespace. All terms in this language are reconstructed, and must be prefixed with `*`. Languages such as Proto-Indo-European and Proto-Germanic are in this category. '''Exception:''' Reconstructed languages can have entries in the mainspace using the ''anti-asterisk'' feature. This requires that both the headword for the mainspace term (as specified using {{para|head}}) and links to the term are prefixed with the anti-asterisk indicator `!!`. * {appendix-constructed}: This language is attested but does not meet the additional requirements set out for constructed languages ([[WT:CFI#Constructed languages]]). Its entries must therefore be in the Appendix namespace, but they are not reconstructed and therefore should not have * prefixed in links. ]==] function Language:getTypes() local types = self._types if types == nil then types = {language = true} if self:getFullCode() == self._code then types.full = true else types["etymology-only"] = true end for t in gmatch(self._data.type, "[^,]+") do types[t] = true end self._types = types end return types end --[==[ Given a list of types as strings, return {true} if the language has all of them. ]==] function Language:hasType(...) Language.hasType = require(language_like_module).hasType return self:hasType(...) end --[==[ Return a list containing `WikimediaLanguage` objects (see [[Module:wikimedia languages]]), which represent languages and their codes as they are used in Wikimedia projects for interwiki linking and such. More than one object may be returned, as a single Wiktionary language may correspond to multiple Wikimedia languages. For example, Wiktionary's single code `sh` (Serbo-Croatian) maps to four Wikimedia codes: `sh` (Serbo-Croatian), `bs` (Bosnian), `hr` (Croatian) and `sr` (Serbian). The code for the Wikimedia language is retrieved from the `wikimedia_codes` property in the data modules. If that property is not present, the code of the current language is used. If none of the available codes is actually a valid Wikimedia code, an empty list is returned. ]==] function Language:getWikimediaLanguages() local wm_langs = self._wikimediaLanguageObjects if wm_langs == nil then local codes = self:getWikimediaLanguageCodes() wm_langs = {} for i = 1, #codes do wm_langs[i] = get_wikimedia_lang(codes[i]) end self._wikimediaLanguageObjects = wm_langs end return wm_langs end function Language:getWikimediaLanguageCodes() local wm_langs = self._wikimediaLanguageCodes if wm_langs == nil then wm_langs = self._data.wikimedia_codes if wm_langs then wm_langs = split(wm_langs, ",", true, true) else local code = self._code if is_known_language_tag(code) then wm_langs = {code} else -- Inherit, but only if no codes are specified in the data *and* -- the language code isn't a valid Wikimedia language code. local parent = self:getParent() wm_langs = parent and parent:getWikimediaLanguageCodes() or {} end end self._wikimediaLanguageCodes = wm_langs end return wm_langs end --[==[ Return the name of the Wikipedia article for the language. `project` specifies the language and project to retrieve the article from, defaulting to {"enwiki"} for the English Wikipedia. Normally if specified it should be the project code for a specific-language Wikipedia e.g. "zhwiki" for the Chinese Wikipedia, but it can be any project, including non-Wikipedia ones. If the project is the English Wikipedia and the property {wikipedia_article} is present in the data module it will be used first. In all other cases, a sitelink will be generated from {:getWikidataItem} (if set). The resulting value (or lack of value) is cached so that subsequent calls are fast. If no value could be determined, and `noCategoryFallback` is {false}, `##Language:getCategoryName` is used as fallback; otherwise, {nil} is returned. Note that if `noCategoryFallback` is {nil} or omitted, it defaults to {false} if the project is the English Wikipedia, otherwise to {true}. In other words, under normal circumstances, if the English Wikipedia article couldn't be retrieved, the return value will fall back to a link to the language's category, but this won't normally happen for any other project. ]==] function Language:getWikipediaArticle(noCategoryFallback, project) Language.getWikipediaArticle = require(language_like_module).getWikipediaArticle return self:getWikipediaArticle(noCategoryFallback, project) end function Language:makeWikipediaLink() return make_link(self, "w:" .. self:getWikipediaArticle(), self:getCanonicalName()) end --[==[ Return the name of the Wikimedia Commons category page for the language. ]==] function Language:getCommonsCategory() Language.getCommonsCategory = require(language_like_module).getCommonsCategory return self:getCommonsCategory() end --[==[ Return the Wikidata item ID for the language or {nil}. This corresponds to the the second field in the data modules. ]==] function Language:getWikidataItem() Language.getWikidataItem = require(language_like_module).getWikidataItem return self:getWikidataItem() end --[==[ Return a list of `Script` objects for all scripts that the language is written in. See [[Module:scripts]]. ]==] function Language:getScripts() local scripts = self._scriptObjects if scripts == nil then local codes = self:getScriptCodes() if codes[1] == "All" then scripts = load_data(scripts_data_module) else scripts = {} for i = 1, #codes do scripts[i] = get_script(codes[i]) end end self._scriptObjects = scripts end return scripts end --[==[ Return the list of script codes in the language's data file. ]==] function Language:getScriptCodes() local scripts = self._scriptCodes if scripts == nil then scripts = self._data[4] if scripts then local codes, n = {}, 0 for code in gmatch(scripts, "[^,]+") do n = n + 1 -- Special handling of "Hants", which represents "Hani", "Hant" and "Hans" collectively. if code == "Hants" then codes[n] = "Hani" codes[n + 1] = "Hant" codes[n + 2] = "Hans" n = n + 2 else codes[n] = code end end scripts = codes else scripts = {"None"} end self._scriptCodes = scripts end return scripts end --[==[ Given some text, iterate through the scripts of a given language trying to find the script that best matches the text. Return a {Script} object representing the script. If no match is found at all, it returns the {None} script object. ]==] function Language:findBestScript(text, forceDetect) if not text or text == "" or text == "-" then return get_script("None") end -- Differs from table returned by getScriptCodes, as Hants is not normalized into its constituents. local codes = self._bestScriptCodes if codes == nil then codes = self._data[4] codes = codes and split(codes, ",", true, true) or {"None"} self._bestScriptCodes = codes end local first_sc = codes[1] if first_sc == "All" then return find_best_script_without_lang(text) end local codes_len = #codes if not (forceDetect or first_sc == "Hants" or codes_len > 1) then first_sc = get_script(first_sc) local charset = first_sc.characters return charset and umatch(text, "[" .. charset .. "]") and first_sc or get_script("None") end -- Remove all formatting characters. text = get_plaintext(text) -- Remove all spaces and any ASCII punctuation. Some non-ASCII punctuation is script-specific, so can't be removed. text = ugsub(text, "[%s!\"#%%&'()*,%-./:;?@[\\%]_{}]+", "") if #text == 0 then return get_script("None") end -- Try to match every script against the text, -- and return the one with the most matching characters. local bestcount, bestscript, length = 0 for i = 1, codes_len do local sc = codes[i] -- Special case for "Hants", which is a special code that represents whichever of "Hant" or "Hans" best matches, -- or "Hani" if they match equally. This avoids having to list all three. In addition, "Hants" will be treated -- as the best match if there is at least one matching character, under the assumption that a Han script is -- desirable in terms that contain a mix of Han and other scripts (not counting those which use Jpan or Kore). if sc == "Hants" then local Hani = get_script("Hani") if not Hant_chars then Hant_chars = load_data("Module:zh/data/ts") Hans_chars = load_data("Module:zh/data/st") end local t, s, found = 0, 0 -- This is faster than using mw.ustring.gmatch directly. for ch in gmatch((ugsub(text, "[" .. Hani.characters .. "]", "\255%0")), "\255(.[\128-\191]*)") do found = true if Hant_chars[ch] then t = t + 1 if Hans_chars[ch] then s = s + 1 end elseif Hans_chars[ch] then s = s + 1 else t, s = t + 1, s + 1 end end if found then if t == s then return Hani end return get_script(t > s and "Hant" or "Hans") end else sc = get_script(sc) if not length then length = ulen(text) end -- Count characters by removing everything in the script's charset and comparing to the original length. local charset = sc.characters local count = charset and length - ulen((ugsub(text, "[" .. charset .. "]+", ""))) or 0 if count >= length then return sc elseif count > bestcount then bestcount = count bestscript = sc end end end -- Return best matching script, or otherwise None. return bestscript or get_script("None") end --[==[ Return a `Family` object for the language family that the language belongs to. See [[Module:families]]. ]==] function Language:getFamily() local family = self._familyObject if family == nil then family = self:getFamilyCode() -- If the value is nil, it's cached as false. family = family and get_family(family) or false self._familyObject = family end return family or nil end --[==[ Return the family code in the language's data file. ]==] function Language:getFamilyCode() local family = self._familyCode if family == nil then -- If the value is nil, it's cached as false. family = self._data[3] or false self._familyCode = family end return family or nil end function Language:getFamilyName() local family = self._familyName if family == nil then family = self:getFamily() -- If the value is nil, it's cached as false. family = family and family:getCanonicalName() or false self._familyName = family end return family or nil end do local function check_family(self, family) if type(family) == "table" then family = family:getCode() end if self:getFamilyCode() == family then return true end local self_family = self:getFamily() if self_family:inFamily(family) then return true -- If the family isn't a real family (e.g. creoles) check any ancestors. elseif self_family:inFamily("qfa-not") then local ancestors = self:getAncestors() for _, ancestor in ipairs(ancestors) do if ancestor:inFamily(family) then return true end end end end --[==[ Check whether the language belongs to `family` (which can be a family code or object). A list of objects can be given in place of `family`; in that case, return true if the language belongs to any of the specified families. Note that some languages (in particular, certain creoles) can have multiple immediate ancestors potentially belonging to different families; in that case, return true if the language belongs to any of the specified families. ]==] function Language:inFamily(...) if self:getFamilyCode() == nil then return false end return check_inputs(self, check_family, false, ...) end end function Language:getParent() local parent = self._parentObject if parent == nil then parent = self:getParentCode() -- If the value is nil, it's cached as false. parent = parent and get_by_code(parent, nil, true, true) or false self._parentObject = parent end return parent or nil end function Language:getParentCode() local parent = self._parentCode if parent == nil then -- If the value is nil, it's cached as false. parent = self._data.parent or false self._parentCode = parent end return parent or nil end function Language:getParentName() local parent = self._parentName if parent == nil then parent = self:getParent() -- If the value is nil, it's cached as false. parent = parent and parent:getCanonicalName() or false self._parentName = parent end return parent or nil end function Language:getParentChain() local chain = self._parentChain if chain == nil then chain = {} local parent, n = self:getParent(), 0 while parent do n = n + 1 chain[n] = parent parent = parent:getParent() end self._parentChain = chain end return chain end --[==[ Return a list of the pseudo-families (if any) that a language belongs to. The returned value is a list of Family objects. Pseudo-families are groupings of languages that are not clades but share some geographic, ethnic or other characteristic (e.g. [[:Category:Papuan languages]], [[:Category:Jewish languages]]). ]==] function Language:getPseudoFamilies() self:loadInExtraData() return require(language_like_module).getPseudoFamilies(self) end do local function check_lang(self, lang) for _, parent in ipairs(self:getParentChain()) do if (type(lang) == "string" and lang or lang:getCode()) == parent:getCode() then return true end end end function Language:hasParent(...) return check_inputs(self, check_lang, false, ...) end end --[==[ If the language is etymology-only, iterate through parents until a full language or family is found, and the corresponding object is returned. If the language is a full language, return that language. ]==] function Language:getFull() local full = self._fullObject if full == nil then full = self:getFullCode() full = full == self._code and self or get_by_code(full) self._fullObject = full end return full end --[==[ If the language is an etymology-only language, iterate through parents until a full language or family is found, and the corresponding code is returned. If the language is a full language, return that language's code. ]==] function Language:getFullCode() return self._fullCode or self._code end --[==[ If the language is an etymology-only language, iterate through parents until a full language or family is found, and the corresponding canonical name is returned. If the language is a full language, return that language's canonical name. ]==] function Language:getFullName() local full = self._fullName if full == nil then full = self:getFull():getCanonicalName() self._fullName = full end return full end --[==[ Return a list of {Language} objects for all languages that this language is directly descended from. Generally this is only a single language, but creoles, pidgins and mixed languages can have multiple ancestors. ]==] function Language:getAncestors() local ancestors = self._ancestorObjects if ancestors == nil then ancestors = {} local ancestor_codes = self:getAncestorCodes() if #ancestor_codes > 0 then for _, ancestor in ipairs(ancestor_codes) do insert(ancestors, get_by_code(ancestor, nil, true)) end else local fam = self:getFamily() local protoLang = fam and fam:getProtoLanguage() or nil -- For the cases where the current language is the proto-language -- of its family, or an etymology-only language that is ancestral to that -- proto-language, we need to step up a level higher right from the -- start. if protoLang and ( protoLang:getCode() == self._code or (self:hasType("etymology-only") and protoLang:hasAncestor(self)) ) then fam = fam:getFamily() protoLang = fam and fam:getProtoLanguage() or nil end while not protoLang and not (not fam or fam:getCode() == "qfa-not") do fam = fam:getFamily() protoLang = fam and fam:getProtoLanguage() or nil end insert(ancestors, protoLang) end self._ancestorObjects = ancestors end return ancestors end do -- Avoid a language being its own ancestor via class inheritance. We only need to check for this if the language has -- inherited an ancestor table from its parent, because we never want to drop ancestors that have been explicitly -- set in the data. Recursively iterate over ancestors until we either find self or run out. If self is found, -- return true. local function check_ancestor(self, lang) local codes = lang:getAncestorCodes() if not codes then return nil end for i = 1, #codes do local code = codes[i] if code == self._code then return true end local anc = get_by_code(code, nil, true) if check_ancestor(self, anc) then return true end end end --[==[ Return a list of `Language` codes for all languages that this language is directly descended from. Generally this is only a single language, but creoles, pidgins and mixed languages can have multiple ancestors. ]==] function Language:getAncestorCodes() if self._ancestorCodes then return self._ancestorCodes end local data = self._data local codes = data.ancestors if codes == nil then codes = {} self._ancestorCodes = codes return codes end codes = split(codes, ",", true, true) self._ancestorCodes = codes -- If there are no codes or the ancestors weren't inherited data, there's nothing left to check. if #codes == 0 or self:getData(false, "raw").ancestors ~= nil then return codes end local i = 1 while i <= #codes do if check_ancestor(self, self) then remove(codes, i) else i = i + 1 end end return codes end end --[==[ Given a list of language objects or codes, return true if at least one of them is an ancestor. This includes any etymology-only children of that ancestor. If the language's ancestor(s) are etymology-only languages, it will also return true for those language parent(s) (e.g. if Vulgar Latin is the ancestor, it will also return true for its parent, Latin). However, a parent is excluded from this if the ancestor is also ancestral to that parent (e.g. if Classical Persian is the ancestor, Persian would return {false}, because Classical Persian is also ancestral to Persian). ]==] function Language:hasAncestor(...) local function iterateOverAncestorTree(node, func, parent_check) local ancestors = node:getAncestors() local ancestorsParents = {} for _, ancestor in ipairs(ancestors) do -- When checking the parents of the other language, and the ancestor is also a parent, skip to the next -- ancestor, so that we exclude any etymology-only children of that parent that are not directly related -- (see below). local ret = (parent_check or not node:hasParent(ancestor)) and func(ancestor) or iterateOverAncestorTree(ancestor, func, parent_check) if ret then return ret end end -- Check the parents of any ancestors. We don't do this if checking the parents of the other language, so that -- we exclude any etymology-only children of those parents that are not directly related (e.g. if the ancestor -- is Vulgar Latin and we are checking New Latin, we want it to return false because they are on different -- ancestral branches. As such, if we're already checking the parent of New Latin (Latin) we don't want to -- compare it to the parent of the ancestor (Latin), as this would be a false positive; it should be one or the -- other). if not parent_check then return nil end for _, ancestor in ipairs(ancestors) do local ancestorParents = ancestor:getParentChain() for _, ancestorParent in ipairs(ancestorParents) do if ancestorParent:getCode() == self._code or ancestorParent:hasAncestor(ancestor) then break else insert(ancestorsParents, ancestorParent) end end end for _, ancestorParent in ipairs(ancestorsParents) do local ret = func(ancestorParent) if ret then return ret end end end local function do_iteration(otherlang, parent_check) -- otherlang can't be self if (type(otherlang) == "string" and otherlang or otherlang:getCode()) == self._code then return false end repeat if iterateOverAncestorTree( self, function(ancestor) return ancestor:getCode() == (type(otherlang) == "string" and otherlang or otherlang:getCode()) end, parent_check ) then return true elseif type(otherlang) == "string" then otherlang = get_by_code(otherlang, nil, true) end otherlang = otherlang:getParent() parent_check = false until not otherlang end local parent_check = true for _, otherlang in ipairs{...} do local ret = do_iteration(otherlang, parent_check) if ret then return true end end return false end do local function construct_node(lang, memo) local branch, ancestors = {lang = lang:getCode()} memo[lang:getCode()] = branch for _, ancestor in ipairs(lang:getAncestors()) do if ancestors == nil then ancestors = {} end insert(ancestors, memo[ancestor:getCode()] or construct_node(ancestor, memo)) end branch.ancestors = ancestors return branch end function Language:getAncestorChain() local chain = self._ancestorChain if chain == nil then chain = construct_node(self, {}) self._ancestorChain = chain end return chain end end function Language:getAncestorChainOld() local chain = self._ancestorChain if chain == nil then chain = {} local step = self while true do local ancestors = step:getAncestors() step = #ancestors == 1 and ancestors[1] or nil if not step then break end insert(chain, step) end self._ancestorChain = chain end return chain end local function fetch_descendants(self, fmt) local descendants, family = {}, self:getFamily() -- Iterate over all three datasets. for _, data in ipairs{ require("Module:languages/code to canonical name"), require("Module:etymology languages/code to canonical name"), require("Module:families/code to canonical name"), } do for code in pairs(data) do local lang = get_by_code(code, nil, true, true) if not lang then error(("Internal error: code '%s' can't be fetched"):format(code)) end -- Test for a descendant. Earlier tests weed out most candidates, while the more intensive tests are only used sparingly. if ( code ~= self._code and -- Not self. lang:inFamily(family) and -- In the same family. ( family:getProtoLanguageCode() == self._code or -- Self is the protolanguage. self:hasDescendant(lang) or -- Full hasDescendant check. (lang:getFullCode() == self._code and not self:hasAncestor(lang)) -- Etymology-only child which isn't an ancestor. ) ) then if fmt == "object" then insert(descendants, lang) elseif fmt == "code" then insert(descendants, code) elseif fmt == "name" then insert(descendants, lang:getCanonicalName()) end end end end return descendants end function Language:getDescendants() local descendants = self._descendantObjects if descendants == nil then descendants = fetch_descendants(self, "object") self._descendantObjects = descendants end return descendants end function Language:getDescendantCodes() local descendants = self._descendantCodes if descendants == nil then descendants = fetch_descendants(self, "code") self._descendantCodes = descendants end return descendants end function Language:getDescendantNames() local descendants = self._descendantNames if descendants == nil then descendants = fetch_descendants(self, "name") self._descendantNames = descendants end return descendants end do local function check_lang(self, lang) if type(lang) == "string" then lang = get_by_code(lang, nil, true) end if lang:hasAncestor(self) then return true end end function Language:hasDescendant(...) return check_inputs(self, check_lang, false, ...) end end local function fetch_children(self, fmt) local m_etym_data = require(etymology_languages_data_module) local self_code, children = self._code, {} for code, lang in pairs(m_etym_data) do local _lang = lang repeat local parent = _lang.parent if parent == self_code then if fmt == "object" then insert(children, get_by_code(code, nil, true)) elseif fmt == "code" then insert(children, code) elseif fmt == "name" then insert(children, lang[1]) end break end _lang = m_etym_data[parent] until not _lang end return children end function Language:getChildren() local children = self._childObjects if children == nil then children = fetch_children(self, "object") self._childObjects = children end return children end function Language:getChildrenCodes() local children = self._childCodes if children == nil then children = fetch_children(self, "code") self._childCodes = children end return children end function Language:getChildrenNames() local children = self._childNames if children == nil then children = fetch_children(self, "name") self._childNames = children end return children end function Language:hasChild(...) local lang = ... if not lang then return false elseif type(lang) == "string" then lang = get_by_code(lang, nil, true) end if lang:hasParent(self) then return true end return self:hasChild(select(2, ...)) end --[==[ Return the name of the main category of that language. Example: {French language"} for French, whose category is at [[:Category:French language]]. Unless optional argument `nocap` is given, the language name at the beginning of the returned value will be capitalized. This capitalization is correct for category names, but not if the language name is lowercase and the returned value of this function is used in the middle of a sentence. ]==] function Language:getCategoryName(nocap) local name = self._categoryName if name == nil then name = self:getCanonicalName() -- If a substrate, omit any leading article. if self:getFamilyCode() == "qfa-sub" then name = name:gsub("^the ", ""):gsub("^a ", "") end -- Only add " language" if a full language. if self:hasType("full") then -- Unless the canonical name already ends with "language", "lect" or their derivatives, add " language". if not (match(name, "[Ll]anguage$") or match(name, "[Ll]ect$")) then name = name .. " language" end end self._categoryName = name end if nocap then return name end return mw.getContentLanguage():ucfirst(name) end --[==[ Create a link to the category; the link text is the canonical name. ]==] function Language:makeCategoryLink() return make_link(self, ":Category:" .. self:getCategoryName(), self:getDisplayForm()) end function Language:getStandardCharacters(sc) local standard_chars = self._data.standard_chars if type(standard_chars) ~= "table" then return standard_chars elseif sc and type(sc) ~= "string" then check_object("script", nil, sc) sc = sc:getCode() end if (not sc) or sc == "None" then local scripts = {} for _, script in pairs(standard_chars) do insert(scripts, script) end return concat(scripts) end if standard_chars[sc] then return standard_chars[sc] .. (standard_chars[1] or "") end end --[==[ Strip diacritics from display text `text` (in a language-specific fashion), which is in the script `sc`. If `sc` is omitted or {nil}, the script is autodetected. This also strips certain punctuation characters from the end and (in the case of Spanish upside-down question mark and exclamation points) from the beginning; strips any whitespace at the end of the text or between the text and final stripped punctuation characters; and applies some language-specific Unicode normalizations to replace discouraged characters with their prescribed alternatives. Return the stripped text. ]==] function Language:stripDiacritics(text, sc) if (not text) or text == "" then return text end sc = checkScript(text, self, sc) text = normalize(text, sc) -- FIXME, rename makeEntryName to stripDiacritics and get rid of second and third return values -- everywhere local _ text, _, _ = iterateSectionSubstitutions(self, text, sc, nil, nil, self._data.strip_diacritics or self._data.entry_name, "strip_diacritics", "stripDiacritics") text = umatch(text, "^[¿¡]?(.-[^%s%p].-)%s*[؟?!;՛՜ ՞ ՟?!︖︕।॥။၊་།]?$") or text return text end --[==[ Convert a ''logical'' pagename (the pagename as it appears to the user, after diacritics and punctuation have been stripped) to a ''physical'' pagename (the pagename as it appears in the MediaWiki database). Reasons for a difference between the two are (a) unsupported titles such as `[ ]` (with square brackets in them), `#` (pound/hash sign) and `¯\_(ツ)_/¯` (with underscores), as well as overly long titles of various sorts; (b) "mammoth" pages that are split into parts (e.g. `a`, which is split into physical pagenames `a/languages A to L` and `a/languages M to Z`). For almost all purposes, you should work with logical and not physical pagenames. But there are certain use cases that require physical pagenames, such as checking the existence of a page or retrieving a page's contents. `pagename` is the logical pagename to be converted. `is_reconstructed_or_appendix` indicates whether the page is in the `Reconstruction` or `Appendix` namespaces. If it is omitted or has the value {nil}, the pagename is checked for an initial asterisk, and if found, the page is assumed to be a `Reconstruction` page. Setting a value of `false` or `true` to `is_reconstructed_or_appendix` disables this check and allows for mainspace pagenames that begin with an asterisk. ]==] function Language:logicalToPhysical(pagename, is_reconstructed_or_appendix) -- FIXME: This probably shouldn't happen but it happens when makeEntryName() receives nil. if pagename == nil then track("nil-passed-to-logicalToPhysical") return nil end local initial_asterisk if is_reconstructed_or_appendix == nil then local pagename_minus_initial_asterisk initial_asterisk, pagename_minus_initial_asterisk = pagename:match("^(%*)(.*)$") if pagename_minus_initial_asterisk then is_reconstructed_or_appendix = true pagename = pagename_minus_initial_asterisk elseif self:hasType("appendix-constructed") then is_reconstructed_or_appendix = true end end if not is_reconstructed_or_appendix then -- Check if the pagename is a listed unsupported title. local unsupportedTitles = load_data(links_data_module).unsupported_titles if unsupportedTitles[pagename] then return "Unsupported titles/" .. unsupportedTitles[pagename] end end -- Set `unsupported` as true if certain conditions are met. local unsupported -- Check if there's an unsupported character. \239\191\189 is the replacement character U+FFFD, which can't be typed -- directly here due to an abuse filter. Unix-style dot-slash notation is also unsupported, as it is used for -- relative paths in links, as are 3 or more consecutive tildes. Note: match is faster with magic -- characters/charsets; find is faster with plaintext. if ( match(pagename, "[#<>%[%]_{|}]") or find(pagename, "\239\191\189") or match(pagename, "%f[^%z/]%.%.?%f[%z/]") or find(pagename, "~~~") ) then unsupported = true -- If it looks like an interwiki link. elseif find(pagename, ":") then local prefix = gsub(pagename, "^:*(.-):.*", ulower) if ( load_data("Module:data/namespaces")[prefix] or load_data("Module:data/interwikis")[prefix] ) then unsupported = true end end -- Escape unsupported characters so they can be used in titles. ` is used as a delimiter for this, so a raw use of -- it in an unsupported title is also escaped here to prevent interference; this is only done with unsupported -- titles, though, so inclusion won't in itself mean a title is treated as unsupported (which is why it's excluded -- from the earlier test). if unsupported then -- FIXME: This conversion needs to be different for reconstructed pages with unsupported characters. There -- aren't any currently, but if there ever are, we need to fix this e.g. to put them in something like -- Reconstruction:Proto-Indo-European/Unsupported titles/`lowbar``num`. local unsupported_characters = load_data(links_data_module).unsupported_characters pagename = pagename:gsub("[#<>%[%]_`{|}\239]\191?\189?", unsupported_characters) :gsub("%f[^%z/]%.%.?%f[%z/]", function(m) return (gsub(m, "%.", "`period`")) end) :gsub("~~~+", function(m) return (gsub(m, "~", "`tilde`")) end) pagename = "Unsupported titles/" .. pagename elseif not is_reconstructed_or_appendix then -- Check if this is a mammoth page. If so, which subpage should we link to? local m_links_data = load_data(links_data_module) local mammoth_page_type = m_links_data.mammoth_pages[pagename] if mammoth_page_type then local canonical_name = self:getFullName() if canonical_name ~= "Translingual" and canonical_name ~= "English" then local this_subpage local L2_sort_key = get_L2_sort_key(canonical_name) for _, subpage_spec in ipairs(m_links_data.mammoth_page_subpage_types[mammoth_page_type]) do -- unpack() fails utterly on data loaded using mw.loadData() even if offsets are given local subpage, pattern = subpage_spec[1], subpage_spec[2] if pattern == true or L2_sort_key:match(pattern) then this_subpage = subpage break end end if not this_subpage then error(("Internal error: Bad data in mammoth_page_subpage_pages in [[Module:links/data]] for mammoth page %s, type %s; last entry didn't have 'true' in it"):format( pagename, mammoth_page_type)) end pagename = pagename .. "/" .. this_subpage end end end return (initial_asterisk or "") .. pagename end --[==[ Strip the diacritics from a display pagename and convert the resulting logical pagename into a physical pagename. This allows you, for example, to retrieve the contents of the page or check its existence. WARNING: This is deprecated and will be going away. It is a simple composition of `##Language:stripDiacritics` and `##Language:logicalToPhysical`; most callers only want the former, and if you need both, call them both yourself. `text` and `sc` are as in `##Language:stripDiacritics`, and `is_reconstructed_or_appendix` is as in `##Language:logicalToPhysical`. ]==] function Language:makeEntryName(text, sc, is_reconstructed_or_appendix) track("makeEntryName called") return self:logicalToPhysical(self:stripDiacritics(text, sc), is_reconstructed_or_appendix) end --[==[ Generate term alternants (e.g. simplified versions of traditional Chinese terms) using a language-specific method, and return them as a table. If the language provides no method for generating alternants, return a table containing only the input term. ]==] function Language:generateAlternants(text, sc) local generate_alternants = self._data.generate_alternants if generate_alternants == nil then return {text} end sc = checkScript(text, self, sc) return require("Module:" .. generate_alternants).generateAlternants(text, self, sc) end --[==[ Create a sort key for the given stripped text, following the rules appropriate for the language. This removes diacritical marks from the stripped text if they are not considered significant for sorting, and may perform some other changes. Any initial hyphen is also removed, and anything in parentheses is removed as well. The `sort_key` setting for each language in the data modules defines the replacements made by this function, or it gives the name of the module that takes the stripped text and returns a sortkey. ]==] function Language:makeSortKey(text, sc) if (not text) or text == "" then return text end if match(text, "<[^<>]+>") then track("track HTML tag") end -- Remove directionality/control characters, bold, italics, soft hyphens, strip markers and HTML tags. -- FIXME: Partly duplicated with remove_formatting() in [[Module:links]]. text = ugsub(text, "[\194\173\216\156\226\128\142\226\128\143\226\128\170-\226\128\174\226\129\166-\226\129\175]", "") text = text:gsub("('*)'''(.-'*)'''", "%1%2"):gsub("('*)''(.-'*)''", "%1%2") text = gsub(unstrip(text), "<[^<>]+>", "") text = decode_uri(text, "PATH") text = checkNoEntities(text) -- Remove initial hyphens and * unless the term only consists of spacing + punctuation characters. text = ugsub(text, "^([􀀀-􏿽]*)[-־ـ᠊*]+([􀀀-􏿽]*)(.*[^%s%p].*)", "%1%2%3") sc = checkScript(text, self, sc) text = normalize(text, sc) text = removeCarets(text, sc) -- For languages with dotted dotless i, ensure that "İ" is sorted as "i", and "I" is sorted as "ı". if self:hasDottedDotlessI() then text = gsub(text, "I\204\135", "i") -- decomposed "İ" :gsub("I", "ı") text = sc:toFixedNFD(text) end -- Convert to lowercase, make the sortkey, then convert to uppercase. Where the language has dotted dotless i, it is -- usually not necessary to convert "i" to "İ" and "ı" to "I" first, because "I" will always be interpreted as -- conventional "I" (not dotless "İ") by any sorting algorithms, which will have been taken into account by the -- sortkey substitutions themselves. However, if no sortkey substitutions have been specified, then conversion is -- necessary so as to prevent "i" and "ı" both being sorted as "I". -- -- An exception is made for scripts that (sometimes) sort by scraping page content, as that means they are sensitive -- to changes in capitalization (as it changes the target page). if not sc:sortByScraping() then text = ulower(text) end local actual_substitution_data, _ -- Don't trim whitespace here because it's significant at the beginning of a sort key or sort base. text, _, actual_substitution_data = iterateSectionSubstitutions(self, text, sc, nil, nil, self._data.sort_key, "sort_key", "makeSortKey", "notrim") if not sc:sortByScraping() then if self:hasDottedDotlessI() and not actual_substitution_data then text = text:gsub("ı", "I"):gsub("i", "İ") text = sc:toFixedNFC(text) end text = uupper(text) end -- Remove parentheses, as long as they are either preceded or followed by something. text = gsub(text, "(.)[()]+", "%1"):gsub("[()]+(.)", "%1") text = escape_risky_characters(text) return text end --[==[ Create the form used as as a basis for display text and transliteration. FIXME: Rename to correctInputText(). ]==] local function processDisplayText(text, self, sc, keepCarets, keepPrefixes) local subbedChars = {} text, subbedChars = doTempSubstitutions(text, subbedChars, keepCarets) text = decode_uri(text, "PATH") text = checkNoEntities(text) sc = checkScript(text, self, sc) text = normalize(text, sc) text, subbedChars = iterateSectionSubstitutions(self, text, sc, subbedChars, keepCarets, self._data.display_text, "display_text", "makeDisplayText") text = removeCarets(text, sc) -- Remove any interwiki link prefixes (unless they have been escaped or this has been disabled). if find(text, ":") and not keepPrefixes then local rep repeat text, rep = gsub(text, "\\\\(\\*:)", "\3%1") until rep == 0 text = gsub(text, "\\:", "\4") while true do local prefix = gsub(text, "^(.-):.+", function(m1) return (gsub(m1, "\244[\128-\191]*", "")) end) -- Check if the prefix is an interwiki, though ignore capitalised Wiktionary:, which is a namespace. if not prefix or prefix == text or prefix == "Wiktionary" or not (load_data("Module:data/interwikis")[ulower(prefix)] or prefix == "") then break end text = gsub(text, "^(.-):(.*)", function(m1, m2) local ret = {} for subbedChar in gmatch(m1, "\244[\128-\191]*") do insert(ret, subbedChar) end return concat(ret) .. m2 end) end text = gsub(text, "\3", "\\"):gsub("\4", ":") end return text, subbedChars end --[==[ Make the display text (i.e. what is displayed on the page). ]==] function Language:makeDisplayText(text, sc, keepPrefixes) if not text or text == "" then return text end local subbedChars text, subbedChars = processDisplayText(text, self, sc, nil, keepPrefixes) text = escape_risky_characters(text) return undoTempSubstitutions(text, subbedChars) end --[==[ Transliterate the text from the given script into Latin script (see [[Wiktionary:Transliteration and romanization]]). The language must have the `translit` property for this to work; if it is not present, {nil} is returned. The `sc` parameter is handled by the transliteration module, and how it is handled is specific to that module. Some transliteration modules may tolerate {nil} as the script, others require it to be one of the possible scripts that the module can transliterate, and will throw an error if it's not one of them. For this reason, the `sc` parameter should always be provided when writing non-language-specific code. The `module_override` parameter is used to override the default module that is used to provide the transliteration. This is useful in cases where you need to demonstrate a particular module in use, but there is no default module yet, or you want to demonstrate an alternative version of a transliteration module before making it official. It should not be used in real modules or templates, only for testing. All uses of this parameter are tracked by [[Wiktionary:Tracking/languages/module_override]]. '''Known bugs''': * This function assumes {tr(s1) .. tr(s2) == tr(s1 .. s2)}. When this assertion fails, wikitext markups like <nowiki>'''</nowiki> can cause wrong transliterations. * HTML entities like `&amp;apos;`, often used to escape wikitext markups, do not work. ]==] function Language:transliterate(text, sc, module_override) -- If there is no text, or the language doesn't have transliteration data and there's no override, return nil. if not text or text == "" or text == "-" then return text end -- If the script is not transliteratable (and no override is given), return nil. sc = checkScript(text, self, sc) if not (sc:isTransliterated() or module_override) then -- temporary tracking to see if/when this gets triggered track("non-transliterable") track("non-transliterable/" .. self._code) track("non-transliterable/" .. sc:getCode()) track("non-transliterable/" .. sc:getCode() .. "/" .. self._code) return nil end -- Remove any strip markers. text = unstrip(text) -- Do not process the formatting into PUA characters for certain languages. local processed = load_data(languages_data_module).substitution[self._code] ~= "none" -- Get the display text with the keepCarets flag set. local subbedChars if processed then text, subbedChars = processDisplayText(text, self, sc, true) end -- Transliterate (using the module override if applicable). text, subbedChars = iterateSectionSubstitutions(self, text, sc, subbedChars, true, module_override or self._data.translit, "translit", "tr") if not text then return nil end -- Incomplete transliterations return nil. local charset = sc.characters if charset and umatch(text, "[" .. charset .. "]") then -- Remove any characters in Latin, which includes Latin characters also included in other scripts (as these are -- false positives), as well as any PUA substitutions. Anything remaining should only be script code "None" -- (e.g. numerals). local check_text = ugsub(text, "[" .. get_script("Latn").characters .. "􀀀-􏿽]+", "") -- Set none_is_last_resort_only flag, so that any non-None chars will cause a script other than "None" to be -- returned. if find_best_script_without_lang(check_text, true):getCode() ~= "None" then return nil end end if processed then text = escape_risky_characters(text) text = undoTempSubstitutions(text, subbedChars) end -- If allowed for this script, capitalize transliteration letters preceded by ^ and remove the caret. if text and text:find("^", 1, true) then assertCaretCapitalizationAllowed(text, sc) if sc:hasCaretCapitalization() then text = processCarets(text, "%^([\128-\191\244]*%*?)([^\128-\191\244][\128-\191]*)", function(m1, m2) return m1 .. uupper(m2) end) end end -- Track module overrides. if module_override ~= nil then track("module_override") end return text end do local function handle_language_spec(self, spec, sc) local ret = self["_" .. spec] if ret == nil then ret = self._data[spec] if type(ret) == "string" then ret = list_to_set(split(ret, ",", true, true)) end self["_" .. spec] = ret end if type(ret) == "table" then ret = ret[sc:getCode()] end return not not ret end function Language:overrideManualTranslit(sc) return handle_language_spec(self, "override_translit", sc) end function Language:link_tr(sc) return handle_language_spec(self, "link_tr", sc) end end --[==[ Return {true} if the language has a transliteration module, or {false} if it doesn't. ]==] function Language:hasTranslit() return not not self._data.translit end --[==[ Return {true} if the language uses the letters I/ı and İ/i, or {false} if it doesn't.]==] function Language:hasDottedDotlessI() return not not self._data.dotted_dotless_i end function Language:toJSON(opts) local strip_diacritics, strip_diacritics_patterns, strip_diacritics_remove_diacritics = self._data.strip_diacritics if strip_diacritics then if strip_diacritics.from then strip_diacritics_patterns = {} for i, from in ipairs(strip_diacritics.from) do insert(strip_diacritics_patterns, {from = from, to = strip_diacritics.to[i] or ""}) end end strip_diacritics_remove_diacritics = strip_diacritics.remove_diacritics end -- mainCode should only end up non-nil if dontCanonicalizeAliases is passed to make_object(). -- props should either contain zero-argument functions to compute the value, or the value itself. local props = { ancestors = function() return self:getAncestorCodes() end, canonicalName = function() return self:getCanonicalName() end, categoryName = function() return self:getCategoryName("nocap") end, code = self._code, mainCode = self._mainCode, parent = function() return self:getParentCode() end, full = function() return self:getFullCode() end, stripDiacriticsPatterns = strip_diacritics_patterns, stripDiacriticsRemoveDiacritics = strip_diacritics_remove_diacritics, family = function() return self:getFamilyCode() end, aliases = function() return self:getAliases() end, varieties = function() return self:getVarieties() end, otherNames = function() return self:getOtherNames() end, pseudoFamilies = function() return self:getPseudoFamilies() end, scripts = function() return self:getScriptCodes() end, type = function() return keys_to_list(self:getTypes()) end, wikimediaLanguages = function() return self:getWikimediaLanguageCodes() end, wikidataItem = function() return self:getWikidataItem() end, wikipediaArticle = function() return self:getWikipediaArticle(true) end, } local ret = {} for prop, val in pairs(props) do if not opts.skip_fields or not opts.skip_fields[prop] then if type(val) == "function" then ret[prop] = val() else ret[prop] = val end end end -- Use `deep_copy` when returning a table, so that there are no editing restrictions imposed by `mw.loadData`. return opts and opts.lua_table and deep_copy(ret) or to_json(ret, opts) end function export.getDataModuleName(code) local letter = match(code, "^(%l)%l%l?$") return "Module:" .. ( letter == nil and "languages/data/exceptional" or #code == 2 and "languages/data/2" or "languages/data/3/" .. letter ) end get_data_module_name = export.getDataModuleName function export.getExtraDataModuleName(code) return get_data_module_name(code) .. "/extra" end get_extra_data_module_name = export.getExtraDataModuleName do local function make_stack(data) local key_types = { [2] = "unique", aliases = "unique", other_names = "unique", type = "append", varieties = "unique", wikipedia_article = "unique", wikimedia_codes = "unique" } local function __index(self, k) local stack, key_type = getmetatable(self), key_types[k] -- Data that isn't inherited from the parent. if key_type == "unique" then local v = stack[stack[make_stack]][k] if v == nil then local layer = stack[0] if layer then -- Could be false if there's no extra data. v = layer[k] end end return v -- Data that is appended by each generation. elseif key_type == "append" then local parts, offset, n = {}, 0, stack[make_stack] for i = 1, n do local part = stack[i][k] if part == nil then offset = offset + 1 else parts[i - offset] = part end end return offset ~= n and concat(parts, ",") or nil end local n = stack[make_stack] while true do local layer = stack[n] if not layer then -- Could be false if there's no extra data. return nil end local v = layer[k] if v ~= nil then return v end n = n - 1 end end local function __newindex() error("table is read-only") end local function __pairs(self) -- Iterate down the stack, caching keys to avoid duplicate returns. local stack, seen = getmetatable(self), {} local n = stack[make_stack] local iter, state, k, v = pairs(stack[n]) return function() repeat repeat k = iter(state, k) if k == nil then n = n - 1 local layer = stack[n] if not layer then -- Could be false if there's no extra data. return nil end iter, state, k = pairs(layer) end until not (k == nil or seen[k]) -- Get the value via a lookup, as the one returned by the -- iterator will be the raw value from the current layer, -- which may not be the one __index will return for that -- key. Also memoize the key in `seen` (even if the lookup -- returns nil) so that it doesn't get looked up again. -- TODO: store values in `self`, avoiding the need to create -- the `seen` table. The iterator will need to iterate over -- `self` with `next` first to find these on future loops. v, seen[k] = self[k], true until v ~= nil return k, v end end local __ipairs = require(table_module).indexIpairs function make_stack(data) local stack = { data, [make_stack] = 1, -- stores the length and acts as a sentinel to confirm a given metatable is a stack. __index = __index, __newindex = __newindex, __pairs = __pairs, __ipairs = __ipairs, } stack.__metatable = stack return setmetatable({}, stack), stack end return make_stack(data) end local function get_stack(data) local stack = getmetatable(data) return stack and type(stack) == "table" and stack[make_stack] and stack or nil end --[==[ <span style="color: var(--wikt-palette-red,#BA0000)">This function is not for use in entries or other content pages.</span> Return a blob of data about the language. The format of this blob is undocumented, and perhaps unstable; it's intended for things like the module's own unit-tests, which are "close friends" with the module and will be kept up-to-date as the format changes. If `extra` is set, any extra data in the relevant `/extra` module will be included. (Note that it will be included anyway if it has already been loaded into the language object.) If `raw` is set, then the returned data will not contain any data inherited from parent objects. -- Do NOT use these methods! -- All uses should be pre-approved on the talk page! ]==] function Language:getData(extra, raw) if extra then self:loadInExtraData() end local data = self._data -- If raw is not set, just return the data. if not raw then return data end local stack = get_stack(data) -- If there isn't a stack or its length is 1, return the data. Extra data (if any) will be included, as it's -- stored at key 0 and doesn't affect the reported length. if stack == nil then return data end local n = stack[make_stack] if n == 1 then return data end extra = stack[0] -- If there isn't any extra data, return the top layer of the stack. if extra == nil then return stack[n] end -- If there is, return a new stack which has the top layer at key 1 and the extra data at key 0. data, stack = make_stack(stack[n]) stack[0] = extra return data end function Language:loadInExtraData() -- Only full languages have extra data. if not self:hasType("language", "full") then return end local data = self._data -- If there's no stack, create one. local stack = get_stack(self._data) if stack == nil then data, stack = make_stack(data) -- If already loaded, return. elseif stack[0] ~= nil then return end self._data = data -- Load extra data from the relevant module and add it to the stack at key 0, so that the __index and __pairs -- metamethods will pick it up, since they iterate down the stack until they run out of layers. local code = self._code local modulename = get_extra_data_module_name(code) -- No data cached as false. stack[0] = modulename and load_data(modulename)[code] or false end --[==[ Return the name of the module containing the language's data, e.g. [[Module:languages/data/3/k]] for three-letter codes beginning with `k`. ]==] function Language:getDataModuleName() local name = self._dataModuleName if name == nil then name = self:hasType("etymology-only") and etymology_languages_data_module or get_data_module_name(self._mainCode or self._code) self._dataModuleName = name end return name end --[==[ Return the name of the module containing the language's extra data, e.g. [[Module:languages/data/3/k/extra]] for three-letter codes beginning with `k`. ]==] function Language:getExtraDataModuleName() local name = self._extraDataModuleName if name == nil then name = not self:hasType("etymology-only") and get_extra_data_module_name(self._mainCode or self._code) or false self._extraDataModuleName = name end return name or nil end function export.makeObject(code, data, dontCanonicalizeAliases) local data_type = type(data) if data_type ~= "table" then error(("bad argument #2 to 'makeObject' (table expected, got %s)"):format(data_type)) end -- Convert any aliases. local input_code = code code = normalize_code(code) input_code = dontCanonicalizeAliases and input_code or code local parent if data.parent then parent = get_by_code(data.parent, nil, true, true) else parent = Language end parent.__index = parent local lang = {_code = input_code} -- This can only happen if dontCanonicalizeAliases is passed to make_object(). if code ~= input_code then lang._mainCode = code end local parent_data = parent._data if parent_data == nil then -- Full code is the same as the code. lang._fullCode = parent._code or code else -- Copy full code. lang._fullCode = parent._fullCode local stack = get_stack(parent_data) if stack == nil then parent_data, stack = make_stack(parent_data) end -- Insert the input data as the new top layer of the stack. local n = stack[make_stack] + 1 data, stack[n], stack[make_stack] = parent_data, data, n end lang._data = data return setmetatable(lang, parent) end make_object = export.makeObject end --[==[ Find the language whose code matches the one provided. If it exists, return a `Language` object representing the language. Otherwise, return {nil}, unless `paramForError` is given, in which case an error is generated. If `paramForError` is {true}, a generic error message mentioning the bad code is generated; otherwise `paramForError` should be a string or number specifying the parameter that the code came from, and this parameter will be mentioned in the error message along with the bad code. If `allowEtymLang` is specified, etymology-only language codes are allowed and looked up along with full language codes. If `allowFamily` is specified, language family codes are allowed and looked up along with normal language codes. ]==] function export.getByCode(code, paramForError, allowEtymLang, allowFamily) -- [[ Track uses of paramForError, ultimately so it can be removed, as error-handling should be done by -- [[Module:parameters]], not here. ]] -- I (Benwing) disagree; that is an idealistic goal unlikely to be achievable -- realistically. if paramForError ~= nil then track("paramForError") end if type(code) ~= "string" then local typ if not code then typ = "nil" elseif check_object("language", true, code) then typ = "a language object" elseif check_object("family", true, code) then typ = "a family object" else typ = "a " .. type(code) end error("The function getByCode expects a string as its first argument, but received " .. typ .. ".") end local m_data = load_data(languages_data_module) if m_data.aliases[code] or m_data.track[code] then track(code) end local norm_code = normalize_code(code) -- Get the data, checking for etymology-only languages if allowEtymLang is set. local data = load_data(get_data_module_name(norm_code))[norm_code] or allowEtymLang and load_data(etymology_languages_data_module)[norm_code] -- If no data was found and allowFamily is set, check the family data. If the main family data was found, make the -- object with [[Module:families]] instead, as family objects have different methods. However, if it's an -- etymology-only family, use make_object in this module (which handles object inheritance), and the -- family-specific methods will be inherited from the parent object. if data == nil and allowFamily then data = load_data("Module:families/data")[norm_code] if data ~= nil then if data.parent == nil then return make_family_object(norm_code, data) elseif not allowEtymLang then data = nil end end end local retval = code and data and make_object(code, data) if not retval and paramForError then require("Module:languages/errorGetBy").code(code, paramForError, allowEtymLang, allowFamily) end return retval end get_by_code = export.getByCode --[==[ Find the language whose canonical name (the name used to represent that language on Wiktionary) or other name matches the one provided. If it exists, return a `Language` object representing the language. Otherwise, return {nil}, unless `paramForError` is given, in which case an error is generated. If `allowEtymLang` is specified, etymology-only language codes are allowed and looked up along with full language codes. If `allowFamily` is specified, language family codes are allowed and looked up along with normal language codes. The canonical name of languages should always be unique (it is an error for two languages on Wiktionary to share the same canonical name), so this is guaranteed to give at most one result. This function is powered by [[Module:languages/canonical names]], which contains a pre-generated mapping of full-language canonical names to codes. It is generated by going through the [[:Category:Language data modules]] for full languages. When `allowEtymLang` is specified for the above function, [[Module:etymology languages/canonical names]] may also be used, and when `allowFamily` is specified for the above function, [[Module:families/canonical names]] may also be used. ]==] function export.getByCanonicalName(name, errorIfInvalid, allowEtymLang, allowFamily) local byName = load_data("Module:languages/canonical names") local code = byName and byName[name] if not code and allowEtymLang then byName = load_data("Module:etymology languages/canonical names") code = byName and byName[name] or byName[gsub(name, " [Ss]ubstrate$", "")] or byName[gsub(name, "^a ", "")] or byName[gsub(name, "^a ", ""):gsub(" [Ss]ubstrate$", "")] or -- For etymology families like "ira-pro". -- FIXME: This is not ideal, as it allows " languages" to be appended to any etymology-only language, too. byName[match(name, "^(.*) languages$")] end if not code and allowFamily then byName = load_data("Module:families/canonical names") code = byName[name] or byName[match(name, "^(.*) languages$")] end local retval = code and get_by_code(code, errorIfInvalid, allowEtymLang, allowFamily) if not retval and errorIfInvalid then require("Module:languages/errorGetBy").canonicalName(name, allowEtymLang, allowFamily) end return retval end --[==[ Used by [[Module:languages/data/2]] (et al.) and [[Module:etymology languages/data]], [[Module:families/data]], [[Module:scripts/data]] and [[Module:writing systems/data]] to finalize the data into the format that is actually returned. ]==] function export.finalizeData(data, main_type, variety) local fields = {"type"} if main_type == "language" then insert(fields, 4) -- script codes insert(fields, "ancestors") insert(fields, "link_tr") insert(fields, "override_translit") insert(fields, "wikimedia_codes") elseif main_type == "script" then insert(fields, 3) -- writing system codes end -- Families and writing systems have no extra fields to process. local fields_len = #fields for _, entity in next, data do if variety then -- Move parent from 3 to "parent" and family from "family" to 3. These are different for the sake of -- convenience, since very few varieties have the family specified, whereas all of them have a parent. entity.parent, entity[3] = entity[3], entity.family entity.family = nil -- Give the type "regular" iff not a variety and no other types are assigned. elseif not (entity.type or entity.parent) then entity.type = "regular" end for i = 1, fields_len do local key = fields[i] local field = entity[key] if field and type(field) == "string" then entity[key] = gsub(field, "%s*,%s*", ",") end end end return data end --[==[ For backwards compatibility only; modules should require the error themselves. ]==] function export.err(lang_code, param, code_desc, template_tag, not_real_lang) return require("Module:languages/error")(lang_code, param, code_desc, template_tag, not_real_lang) end return export q5y9zfi7sgh7ioj2ivap081a3izt827 393951 393949 2026-10-09T04:57:21Z Ow! That Hurts! 8952 393951 Scribunto text/plain --[==[ intro: This module implements fetching of language-specific information and processing text in a given language. ===Types of languages=== There are two types of languages: full languages and etymology-only languages. The essential difference is that only full languages appear in L2 headings in vocabulary entries, and hence categories like [[:Category:French nouns]] exist only for full languages. Etymology-only languages have either a full language or another etymology-only language as their parent (in the parent-child inheritance sense), and for etymology-only languages with another etymology-only language as their parent, a full language can always be derived by following the parent links upwards. For example, "Canadian French", code `fr-CA`, is an etymology-only language whose parent is the full language "French", code `fr`. An example of an etymology-only language with another etymology-only parent is "Northumbrian Old English", code `ang-nor`, which has "Anglian Old English", code `ang-ang` as its parent; this is an etymology-only language whose parent is "Old English", code `ang`, which is a full language. (This is because Northumbrian Old English is considered a variety of Anglian Old English.) Sometimes the parent is the "Undetermined" language, code `und`; this is the case, for example, for "substrate" languages such as "Pre-Greek", code `qsb-grc`, and "the BMAC substrate", code `qsb-bma`. It is important to distinguish language ''parents'' from language ''ancestors''. The parent-child relationship is one of containment, i.e. if X is a child of Y, X is considered a variety of Y. On the other hand, the ancestor-descendant relationship is one of descent in time. For example, "Classical Latin", code `la-cla`, and "Late Latin", code `la-lat`, are both etymology-only languages with "Latin", code `la`, as their parents, because both of the former are varieties of Latin. However, Late Latin does *NOT* have Classical Latin as its parent because Late Latin is *not* a variety of Classical Latin; rather, it is a descendant. There is in fact a separate `ancestors` field that is used to express the ancestor-descendant relationship, and Late Latin's ancestor is given as Classical Latin. It is also important to note that sometimes an etymology-only language is actually the conceptual ancestor of its parent language. This happens, for example, with "Old Italian" (code `roa-oit`), which is an etymology-only variant of full language "Italian" (code `it`), and with "Old Latin" (code `itc-ola`), which is an etymology-only variant of Latin. In both cases, the full language has the etymology-only variant listed as an ancestor. This allows a Latin term to inherit from Old Latin using the {{tl|inh}} template (where in this template, "inheritance" refers to ancestral inheritance, i.e. inheritance in time, rather than in the parent-child sense); likewise for Italian and Old Italian. Full languages come in three subtypes: * {regular}: This indicates a full language that is attested according to [[WT:CFI]] and therefore permitted in the main namespace. There may also be reconstructed terms for the language, which are placed in the {Reconstruction} namespace and must be prefixed with * to indicate a reconstruction. Most full languages are natural (not constructed) languages, but a few constructed languages (e.g. Esperanto and Volapük, among others) are also allowed in the mainspace and considered regular languages. * {reconstructed}: This language is not attested according to [[WT:CFI]], and therefore is allowed only in the {Reconstruction} namespace. All terms in this language are reconstructed, and must be prefixed with *. Languages such as Proto-Indo-European and Proto-Germanic are in this category. * {appendix-constructed}: This language is attested but does not meet the additional requirements set out for constructed languages ([[WT:CFI#Constructed languages]]). Its entries must therefore be in the Appendix namespace, but they are not reconstructed and therefore should not have * prefixed in links. Most constructed languages are of this subtype. Both full languages and etymology-only languages have a {Language} object associated with them, which is fetched using the {getByCode} function in [[Module:languages]] to convert a language code to a {Language} object. Depending on the options supplied to this function, etymology-only languages may or may not be accepted, and family codes may be accepted (returning a {Family} object as described in [[Module:families]]). There are also separate {getByCanonicalName} functions in [[Module:languages]] and [[Module:etymology languages]] to convert a language's canonical name to a {Language} object (depending on whether the canonical name refers to a full or etymology-only language). ===Textual representations=== Textual strings belonging to a given language come in several different ''text variants'': # The ''input text'' is what the user supplies in wikitext, in the parameters to {{tl|m}}, {{tl|l}}, {{tl|ux}}, {{tl|t}}, {{tl|lang}} and the like. # The ''corrected input text'' is the input text with some corrections and/or normalizations applied, such as bad-character replacements for certain languages, like replacing `l` or `1` to [[palochka]] in some languages written in Cyrillic. (FIXME: This currently goes under the name ''display text'' but that will be repurposed below. Also, [[User:Surjection]] suggests renaming this to ''normalized input text'', but "normalized" is used in a different sense in [[Module:usex]].) # The ''display text'' is the text in the form as it will be displayed to the user. This is what appears in headwords, in usexes, in displayed internal links, etc. This can include accent marks that are removed to form the stripped display text (see below), as well as embedded bracketed links that are variously processed further. The display text is generated from the corrected input text by applying language-specific transformations; for most languages, there will be no such transformations. The general reason for having a difference between input and display text is to allow for extra information in the input text that is not displayed to the user but is sent to the transliteration module. Note that having different display and input text is only supported currently through special-casing but will be generalized. Examples of transformations are: (1) Removing the {{cd|^}} that is used in certain East Asian (and possibly other unicameral) languages to indicate capitalization of the transliteration (which is currently special-cased); (2) for Korean, removing or otherwise processing hyphens (which is currently special-cased); (3) for Arabic, removing a ''sukūn'' diacritic placed over a ''tāʔ marbūṭa'' (like this: ةْ) to indicate that the ''tāʔ marbūṭa'' is pronounced and transliterated as /t/ instead of being silent [NOTE, NOT IMPLEMENTED YET]; (4) for Thai and Khmer, converting space-separated words to bracketed words and resolving respelling substitutions such as `[กรีน/กฺรีน]`, which indicate how to transliterate given words [NOTE, NOT IMPLEMENTED YET except in language-specific templates like {{tl|th-usex}}]. ## The ''right-resolved display text'' is the result of removing brackets around one-part embedded links and resolving two-part embedded links into their right-hand components (i.e. converting two-part links into the displayed form). The process of right-resolution is what happens when you call {{cd|remove_links()}} in [[Module:links]] on some text. When applied to the display text, it produces exactly what the user sees, without any link markup. # The ''stripped display text'' is the result of applying diacritic-stripping to the display text. ## The ''left-resolved stripped display text'' [NEED BETTER NAME] is the result of applying left-resolution to the stripped display text, i.e. similar to right-resolution but resolving two-part embedded links into their left-hand components (i.e. the linked-to page). If the display text refers to a single page, the resulting of applying diacritic stripping and left-resolution produces the ''logical pagename''. # The ''physical pagename text'' is the result of converting the stripped display text into physical page links. If the stripped display text contains embedded links, the left side of those links is converted into physical page links; otherwise, the entire text is considered a pagename and converted in the same fashion. The conversion does three things: (1) converts characters not allowed in pagenames into their "unsupported title" representation, e.g. {{cd|Unsupported titles/`gt`}} in place of the logical name {{cd|>}}; (2) handles certain special-cased unsupported-title logical pagenames, such as {{cd|Unsupported titles/Space}} in place of {{cd|[space]}} and {{cd|Unsupported titles/Ancient Greek dish}} in place of a very long Greek name for a gourmet dish as found in Aristophanes; (3) converts "mammoth" pagenames such as [[a]] into their appropriate split component, e.g. [[a/languages A to L]]. # The ''source translit text'' is the text as supplied to the language-specific {{cd|transliterate()}} method. The form of the source translit text may need to be language-specific, e.g Thai and Khmer will need the corrected input text, whereas other languages may need to work off the display text. [FIXME: It's still unclear to me how embedded bracketed links are handled in the existing code.] In general, embedded links need to be right-resolved (see above), but when this happens is unclear to me [FIXME]. Some languages have a chop-up-and-paste-together scheme that sends parts of the text through the transliterate mechanism, and for others (those listed with "cont" in {{cd|substitution}} in [[Module:tungumál/gögn]]) they receive the full input text, but preprocessed in certain ways. (The wisdom of this is still unclear to me.) # The ''transliterated text'' (or ''transliteration'') is the result of transliterating the source translit text. Unlike for all the other text variants except the transcribed text, it is always in the Latin script. # The ''transcribed text'' (or ''transcription'') is the result of transcribing the source translit text, where "transcription" here means a close approximation to the phonetic form of the language in languages (e.g. Akkadian, Sumerian, Ancient Egyptian, maybe Tibetan) that have a wide difference between the written letters and spoken form. Unlike for all the other text variants other than the transliterated text, it is always in the Latin script. Currently, the transcribed text is always supplied manually be the user; there is no such thing as a {{cd|transcribe()}} method on language objects. # The ''sort key'' is the text used in sort keys for determining the placing of pages in categories they belong to. The sort key is generated from the pagename or a specified ''sort base'' by lowercasing, doing language-specific transformations and then uppercasing the result. If the sort base is supplied and is generated from input text, it needs to be converted to display text, have embedded links removed through right-resolution and have diacritic-stripping applied. # There are other text variants that occur in usexes (specifically, there are normalized variants of several of the above text variants), but we can skip them for now. The following methods exist on {Language} objects to convert between different text variants: # {correctInputText} (currently called {makeDisplayText}): This converts input text to corrected input text. # {stripDiacritics}: This converts to stripped display text. [FIXME: This needs some rethinking. In particular, {stripDiacritics} is sometimes called on input text, corrected input text or display text (in various paths inside of [[Module:links]], and, in the case of input text, usually from other modules). We need to make sure we don't try to convert input text to display text twice, but at the same time we need to support calling it directly on input text since so many modules do this. This means we need to add a parameter indicating whether the passed-in text is input, corrected input, or display text; if the former two, we call {correctInputText} ourselves.] # {logicalToPhysical}: This converts logical pagenames to physical pagenames. # {transliterate}: This appears to convert input text with embedded brackets removed into a transliteration. [FIXME: This needs some rethinking. In particular, it calls {processDisplayText} on its input, which won't work for Thai and Khmer, so we may need language-specific flags indicating whether to pass the input text directly to the language transliterate method. In addition, I'm not sure how embedded links are handled in the existing translit code; a lot of callers remove the links themselves before calling {transliterate()}, which I assume is wrong.] # {makeSortKey}: This converts display text (?) to a sort key. [FIXME: Clarify this.] ]==] local export = {} local debug_track_module = "Module:debug/track" local etymology_languages_data_module = "Module:etymology languages/data" local families_module = "Module:families" local headword_page_module = "Module:headword/page" local json_module = "Module:JSON" local language_like_module = "Module:language-like" local languages_data_module = "Module:tungumál/gögn" local languages_data_patterns_module = "Module:languages/data/patterns" local links_data_module = "Module:links/data" local load_module = "Module:load" local scripts_module = "Module:scripts" local scripts_data_module = "Module:scripts/data" local string_encode_entities_module = "Module:string/encode entities" local string_pattern_escape_module = "Module:string/patternEscape" local string_replacement_escape_module = "Module:string/replacementEscape" local string_utilities_module = "Module:string utilities" local table_module = "Module:table" local utilities_module = "Module:utilities" local wikimedia_languages_module = "Module:wikimedia languages" local mw = mw local string = string local table = table local char = string.char local concat = table.concat local find = string.find local floor = math.floor local get_by_code -- Defined below. local get_data_module_name -- Defined below. local get_extra_data_module_name -- Defined below. local getmetatable = getmetatable local gmatch = string.gmatch local gsub = string.gsub local insert = table.insert local ipairs = ipairs local is_known_language_tag = mw.language.isKnownLanguageTag local make_object -- Defined below. local match = string.match local next = next local pairs = pairs local remove = table.remove local require = require local select = select local setmetatable = setmetatable local sub = string.sub local type = type local unstrip = mw.text.unstrip -- Loaded as needed by findBestScript. local Hans_chars local Hant_chars local function check_object(...) check_object = require(utilities_module).check_object return check_object(...) end local function debug_track(...) debug_track = require(debug_track_module) return debug_track(...) end local function decode_entities(...) decode_entities = require(string_utilities_module).decode_entities return decode_entities(...) end local function decode_uri(...) decode_uri = require(string_utilities_module).decode_uri return decode_uri(...) end local function deep_copy(...) deep_copy = require(table_module).deepCopy return deep_copy(...) end local function encode_entities(...) encode_entities = require(string_encode_entities_module) return encode_entities(...) end local function get_L2_sort_key(...) get_L2_sort_key = require(headword_page_module).get_L2_sort_key return get_L2_sort_key(...) end local function get_script(...) get_script = require(scripts_module).getByCode return get_script(...) end local function find_best_script_without_lang(...) find_best_script_without_lang = require(scripts_module).findBestScriptWithoutLang return find_best_script_without_lang(...) end local function get_family(...) get_family = require(families_module).getByCode return get_family(...) end local function get_plaintext(...) get_plaintext = require(utilities_module).get_plaintext return get_plaintext(...) end local function get_wikimedia_lang(...) get_wikimedia_lang = require(wikimedia_languages_module).getByCode return get_wikimedia_lang(...) end local function keys_to_list(...) keys_to_list = require(table_module).keysToList return keys_to_list(...) end local function list_to_set(...) list_to_set = require(table_module).listToSet return list_to_set(...) end local function load_data(...) load_data = require(load_module).load_data return load_data(...) end local function make_family_object(...) make_family_object = require(families_module).makeObject return make_family_object(...) end local function pattern_escape(...) pattern_escape = require(string_pattern_escape_module) return pattern_escape(...) end local function replacement_escape(...) replacement_escape = require(string_replacement_escape_module) return replacement_escape(...) end local function safe_require(...) safe_require = require(load_module).safe_require return safe_require(...) end local function shallow_copy(...) shallow_copy = require(table_module).shallowCopy return shallow_copy(...) end local function split(...) split = require(string_utilities_module).split return split(...) end local function to_json(...) to_json = require(json_module).toJSON return to_json(...) end local function u(...) u = require(string_utilities_module).char return u(...) end local function ugsub(...) ugsub = require(string_utilities_module).gsub return ugsub(...) end local function ulen(...) ulen = require(string_utilities_module).len return ulen(...) end local function ulower(...) ulower = require(string_utilities_module).lower return ulower(...) end local function umatch(...) umatch = require(string_utilities_module).match return umatch(...) end local function uupper(...) uupper = require(string_utilities_module).upper return uupper(...) end local function track(page) debug_track("languages/" .. page) return true end local function normalize_code(code) return load_data(languages_data_module).aliases[code] or code end local function check_inputs(self, check, default, ...) local n = select("#", ...) if n == 0 then return false end local ret = check(self, (...)) if ret ~= nil then return ret elseif n > 1 then local inputs = {...} for i = 2, n do ret = check(self, inputs[i]) if ret ~= nil then return ret end end end return default end local function make_link(self, target, display) local prefix, main if self:getFamilyCode() == "qfa-sub" then prefix, main = display:match("^(the )(.*)") if not prefix then prefix, main = display:match("^(a )(.*)") end end return (prefix or "") .. "[[" .. target .. "|" .. (main or display) .. "]]" end -- Convert risky characters to HTML entities, which minimizes interference once returned (e.g. for "sms:a", "<!-- -->" etc.). local function escape_risky_characters(text) -- Spacing characters in isolation generally need to be escaped in order to be properly processed by the MediaWiki -- software. if umatch(text, "^%s*$") then return encode_entities(text, text) end return encode_entities(text, "!#%&*+/:;<=>?@[\\]_{|}") end -- Temporarily convert various formatting characters to PUA to prevent them from being disrupted by the substitution process. local function doTempSubstitutions(text, subbedChars, keepCarets, noTrim) -- Clone so that we don't insert any extra patterns into the table in package.loaded. For some reason, using require -- seems to keep memory use down; probably because the table is always cloned. local patterns = shallow_copy(require(languages_data_patterns_module)) if keepCarets then insert(patterns, "((\\+)%^)") insert(patterns, "((%^))") end -- Ensure any whitespace at the beginning and end is temp substituted, to prevent it from being accidentally -- trimmed. We only want to trim any final spaces added during the substitution process (e.g. by a module), which -- means we only do this during the first round of temp substitutions. if not noTrim then insert(patterns, "^([\128-\191\244]*(%s+))") insert(patterns, "((%s+)[\128-\191\244]*)$") end -- Pre-substitution, of "[[" and "]]", which makes pattern matching more accurate. text = gsub(text, "%f[%[]%[%[", "\1"):gsub("%f[%]]%]%]", "\2") local i = #subbedChars for _, pattern in ipairs(patterns) do -- Patterns ending in \0 stand are for things like "[[" or "]]"), so the inserted PUA are treated as breaks -- between terms by modules that scrape info from pages. local term_divider pattern = gsub(pattern, "%z$", function(divider) term_divider = divider == "\0" return "" end) text = gsub(text, pattern, function(...) local m = {...} local m1New = m[1] for k = 2, #m do local n = i + k - 1 subbedChars[n] = m[k] local byte2 = floor(n / 4096) % 64 + (term_divider and 128 or 136) local byte3 = floor(n / 64) % 64 + 128 local byte4 = n % 64 + 128 m1New = gsub(m1New, pattern_escape(m[k]), "\244" .. char(byte2) .. char(byte3) .. char(byte4), 1) end i = i + #m - 1 return m1New end) end text = gsub(text, "\1", "%[%["):gsub("\2", "%]%]") return text, subbedChars end -- Reinsert any formatting that was temporarily substituted. local function undoTempSubstitutions(text, subbedChars) for i = 1, #subbedChars do local byte2 = floor(i / 4096) % 64 + 128 local byte3 = floor(i / 64) % 64 + 128 local byte4 = i % 64 + 128 text = gsub(text, "\244[" .. char(byte2) .. char(byte2+8) .. "]" .. char(byte3) .. char(byte4), replacement_escape(subbedChars[i])) end text = gsub(text, "\1", "%[%["):gsub("\2", "%]%]") return text end -- Check if the raw text is an unsupported title, and if so return that. Otherwise, remove HTML entities. We do the -- pre-conversion to avoid loading the unsupported title list unnecessarily. local function checkNoEntities(text) local textNoEnc = decode_entities(text) if textNoEnc ~= text and load_data(links_data_module).unsupported_titles[text] then return text else return textNoEnc end end -- If no script object is provided (or if it's invalid or None), get one. local function checkScript(text, self, sc) if not check_object("script", true, sc) or sc:getCode() == "None" then return self:findBestScript(text) end return sc end local function normalize(text, sc) text = sc:fixDiscouragedSequences(text) return sc:toFixedNFD(text) end --[=[ Subfunction of iterateSectionSubstitutions(). Process an individual chunk of text according to the specifications in `substitution_data`. The input parameters are all as in the documentation of iterateSectionSubstitutions() except for `recursed`, which is set to true if we called ourselves recursively to process a script-specific setting or script-wide fallback. Returns two values: the processed text and the actual substitution data used to do the substitutions (same as the `actual_substitution_data` return value to iterateSectionSubstitutions()). ]=] local function doSubstitutions(self, text, sc, substitution_data, data_field, function_name, recursed) -- BE CAREFUL in this function because the value at any level can be `false`, which causes no processing to be done -- and blocks any further fallback processing. local actual_substitution_data = substitution_data -- If there are language-specific substitutes given in the data module, use those. if type(substitution_data) == "table" then -- If a script is specified, run this function with the script-specific data before continuing. local sc_code = sc:getCode() local has_substitution_data = false if substitution_data[sc_code] ~= nil then has_substitution_data = true if substitution_data[sc_code] then text, actual_substitution_data = doSubstitutions(self, text, sc, substitution_data[sc_code], data_field, function_name, true) end -- Hant, Hans and Hani are usually treated the same, so add a special case to avoid having to specify each one -- separately. elseif sc_code:match("^Han") and substitution_data.Hani ~= nil then has_substitution_data = true if substitution_data.Hani then text, actual_substitution_data = doSubstitutions(self, text, sc, substitution_data.Hani, data_field, function_name, true) end -- Substitution data with key 1 in the outer table may be given as a fallback. elseif substitution_data[1] ~= nil then has_substitution_data = true if substitution_data[1] then text, actual_substitution_data = doSubstitutions(self, text, sc, substitution_data[1], data_field, function_name, true) end end -- Iterate over all strings in the "from" subtable, and gsub with the corresponding string in "to". We work with -- the NFD decomposed forms, as this simplifies many substitutions. if substitution_data.from then has_substitution_data = true for i, from in ipairs(substitution_data.from) do -- Normalize each loop, to ensure multi-stage substitutions work correctly. text = sc:toFixedNFD(text) text = ugsub(text, sc:toFixedNFD(from), substitution_data.to[i] or "") end end if substitution_data.remove_diacritics then has_substitution_data = true text = sc:toFixedNFD(text) -- Convert exceptions to PUA. local remove_exceptions, substitutes = substitution_data.remove_exceptions if remove_exceptions then substitutes = {} local i = 0 for _, exception in ipairs(remove_exceptions) do exception = sc:toFixedNFD(exception) text = ugsub(text, exception, function(m) i = i + 1 local subst = u(0x80000 + i) substitutes[subst] = m return subst end) end end -- Strip diacritics. text = ugsub(text, "[" .. substitution_data.remove_diacritics .. "]", "") -- Convert exceptions back. if remove_exceptions then text = text:gsub("\242[\128-\191]*", substitutes) end end if not has_substitution_data and sc._data[data_field] then -- If language-specific sort key (etc.) is nil, fall back to script-wide sort key (etc.). text, actual_substitution_data = doSubstitutions(self, text, sc, sc._data[data_field], data_field, function_name, true) end elseif type(substitution_data) == "string" then -- If there is a dedicated function module, use that. local module = safe_require("Module:" .. substitution_data) if module then -- TODO: translit functions should take objects, not codes. -- TODO: translit functions should be called with form NFD. if function_name == "tr" then if not module[function_name] then error(("Internal error: Module [[%s]] has no function named 'tr'"):format(substitution_data)) end text = module[function_name](text, self._code, sc:getCode()) elseif function_name == "stripDiacritics" then -- FIXME, get rid of this arm after renaming makeEntryName -> stripDiacritics. if module[function_name] then text = module[function_name](sc:toFixedNFD(text), self, sc) elseif module.makeEntryName then text = module.makeEntryName(sc:toFixedNFD(text), self, sc) else error(("Internal error: Module [[%s]] has no function named 'stripDiacritics' or 'makeEntryName'" ):format(substitution_data)) end else if not module[function_name] then error(("Internal error: Module [[%s]] has no function named '%s'"):format( substitution_data, function_name)) end text = module[function_name](sc:toFixedNFD(text), self, sc) end else error("Substitution data '" .. substitution_data .. "' does not match an existing module.") end elseif substitution_data == nil and sc._data[data_field] then -- If language-specific sort key (etc.) is nil, fall back to script-wide sort key (etc.). text, actual_substitution_data = doSubstitutions(self, text, sc, sc._data[data_field], data_field, function_name, true) end -- Don't normalize to NFC if this is the inner loop or if a module returned nil. if recursed or not text then return text, actual_substitution_data end -- Fix any discouraged sequences created during the substitution process, and normalize into the final form. return sc:toFixedNFC(sc:fixDiscouragedSequences(text)), actual_substitution_data end --[=[ Split the text into sections, based on the presence of temporarily substituted formatting characters, then iterate over each section to apply substitutions (e.g. transliteration or diacritic stripping). This avoids putting PUA (Private Use Area) characters through language-specific modules, which may be unequipped for them. This function is passed the following values: * `self` (the Language object); * `text` (the text to process); * `sc` (the script of the text, which must be specified; callers should call checkScript() as needed to autodetect the script of the text if not given explicitly by the user); * `subbedChars` (an array of the same length as the text, indicating which characters have been substituted and by what, or {nil} if no substitutions are to happen); * `keepCarets` (DOCUMENT ME); * `substitution_data` (the data indicating which substitutions to apply, taken directly from `data_field` in the language's data structure in a submodule of [[Module:tungumál/gögn]]); * `data_field` (the field from which `substitution_data` was fetched, such as {"sort_key"} or {"strip_diacritics"}); * `function_name` (the name of the function to call to do the substitution, in case `substitution_data` specifies a module to do the substitution); * `notrim` (don't trim whitespace at the edges of `text`; set when computing the sort key, because whitespace at the beginning of a sort key is significant and causes the resulting page to be sorted at the beginning of the category it's in). Return three values: # the processed text; # the value of `subbedChars` that was passed in, possibly modified with additional character substitutions; will be {nil} if {nil} was passed in; # the actual substitution data that was used to apply substitutions to `text`; this may be different from the value of `substitution_data` passed in if that value recursively specified script-specific substitutions or if no substitution data could be found in the language-specific data (e.g. {nil} was passed in or a structure was passed in that had no setting for the script given in `sc`), but a script-wide fallback value was set; currently it is only used by {makeSortKey()}. ]=] local function iterateSectionSubstitutions(self, text, sc, subbedChars, keepCarets, substitution_data, data_field, function_name, notrim) local sections -- See [[Module:tungumál/gögn]]. if not find(text, "\244") or load_data(languages_data_module).substitution[self._code] == "cont" then sections = {text} else sections = split(text, "\244[\128-\143][\128-\191]*", true) end local actual_substitution_data for _, section in ipairs(sections) do -- Don't bother processing empty strings or whitespace (which may also not be handled well by dedicated -- modules). if gsub(section, "%s+", "") ~= "" then local sub, this_actual_substitution_data = doSubstitutions(self, section, sc, substitution_data, data_field, function_name) actual_substitution_data = this_actual_substitution_data -- Second round of temporary substitutions, in case any formatting was added by the main substitution -- process. However, don't do this if the section contains formatting already (as it would have had to have -- been escaped to reach this stage, and therefore should be given as raw text). if sub and subbedChars then local noSub for _, pattern in ipairs(require(languages_data_patterns_module)) do if match(section, pattern .. "%z?") then noSub = true end end if not noSub then sub, subbedChars = doTempSubstitutions(sub, subbedChars, keepCarets, true) end end if not sub then text = sub break end text = sub and gsub(text, pattern_escape(section), replacement_escape(sub), 1) or text end end if not notrim then -- Trim, unless there are only spacing characters, while ignoring any final formatting characters. -- Do not trim sort keys because spaces at the beginning are significant. text = text and text:gsub("^([\128-\191\244]*)%s+(%S)", "%1%2"):gsub("(%S)%s+([\128-\191\244]*)$", "%1%2") or nil end return text, subbedChars, actual_substitution_data end -- Process carets (and any escapes). Default to simple removal, if no pattern/replacement is given. local function processCarets(text, pattern, repl) local rep repeat text, rep = gsub(text, "\\\\(\\*^)", "\3%1") until rep == 0 return (text:gsub("\\^", "\4") :gsub(pattern or "%^", repl or "") :gsub("\3", "\\") :gsub("\4", "^")) end local function hasUnescapedCarets(text) return processCarets(text, "%^", "\1"):find("\1", 1, true) ~= nil end local function assertCaretCapitalizationAllowed(text, sc) if sc:forbidsCaretCapitalization() and hasUnescapedCarets(text) then error(("Caret (^) cannot be used to capitalize transliterations for the %s script " .. "(see caret_capitalization in [[Module:scripts/data]]).") :format(sc:getCanonicalName()), 2) end end -- Remove carets if they are used to capitalize parts of transliterations (unless they have been escaped). local function removeCarets(text, sc) if text:find("^", 1, true) then assertCaretCapitalizationAllowed(text, sc) if sc:hasCaretCapitalization() then return processCarets(text) end end return text end local Language = {} --[==[ Return the language code of the language. Example: {fr"} for French. ]==] function Language:getCode() return self._code end --[==[ Return the canonical name of the language. This is the name used to represent that language on Wiktionary, and is guaranteed to be unique to that language alone. Example: {"French"} for French. ]==] function Language:getCanonicalName() local name = self._name if name == nil then name = self._data[1] self._name = name end return name end --[==[ Return the display form of the language. The display form of a language, family or script is the form it takes when appearing as the <code><var>source</var></code> in categories such as <code>English terms derived from <var>source</var></code> or <code>English given names from <var>source</var></code>, and is also the displayed text in `##makeCategoryLink()` links. For full and etymology-only languages, this is the same as the canonical name, but for families, it reads <code>"<var>name</var> languages"</code> (e.g. {"Indo-Iranian languages"}), and for scripts, it reads <code>"<var>name</var> script"</code> (e.g. {"Arabic script"}). ]==] function Language:getDisplayForm() local form = self._displayForm if form == nil then form = self:getCanonicalName() -- Add article and " substrate" to substrates that lack them. if self:getFamilyCode() == "qfa-sub" then if not (sub(form, 1, 4) == "the " or sub(form, 1, 2) == "a ") then form = "a " .. form end if not match(form, " [Ss]ubstrate") then form = form .. " substrate" end end self._displayForm = form end return form end --[==[ Return the value which should be used in the HTML `lang=` attribute for tagged text in the language. ]==] function Language:getHTMLAttribute(sc, region) local code = self._code if not find(code, "-", 1, true) then return code .. "-" .. sc:getCode() .. (region and "-" .. region or "") end local parent = self:getParent() region = region or match(code, "%f[%u][%u-]+%f[%U]") if parent then return parent:getHTMLAttribute(sc, region) end -- TODO: ISO family codes can also be used. return "mis-" .. sc:getCode() .. (region and "-" .. region or "") end --[==[ Return a list of the aliases that the language is known by, excluding the canonical name. Aliases are synonyms for the language in question. The names are not guaranteed to be unique, in that sometimes more than one language is known by the same name. Example: { {"High German", "New High German", "Deutsch"}} for {{lnl|de}}. ]==] function Language:getAliases() self:loadInExtraData() return require(language_like_module).getAliases(self) end --[==[ Return a list of the known subvarieties of a given language, excluding subvarieties that have been given explicit etymology-only language codes. The names are not guaranteed to be unique, in that sometimes a given name refers to a subvariety of more than one language. Example: { {"Southern Aymara", "Central Aymara"}} for {{lnl|ay}}. Note that the returned value can have nested tables in it, when a subvariety goes by more than one name. Example: { {"North Azerbaijani", "South Azerbaijani", {"Afshar", "Afshari", "Afshar Azerbaijani", "Afchar"}, {"Qashqa'i", "Qashqai", "Kashkay"}, "Sonqor"}} for {{lnl|az}}. Here, for example, Afshar, Afshari, Afshar Azerbaijani and Afchar all refer to the same subvariety, whose preferred name is Afshar (the one listed first). To avoid a return value with nested tables in it, specify a non-{nil} value for the `flatten` parameter; in that case, the return value would be { {"North Azerbaijani", "South Azerbaijani", "Afshar", "Afshari", "Afshar Azerbaijani", "Afchar", "Qashqa'i", "Qashqai", "Kashkay", "Sonqor"}}. ]==] function Language:getVarieties(flatten) self:loadInExtraData() return require(language_like_module).getVarieties(self, flatten) end --[==[ Return a table of the "other names" that the language is known by, which are listed in the `other_names` field in the language's extra-data module. It should be noted that the `other_names` field itself is deprecated, and entries listed there should eventually be moved to either `aliases` or `varieties`, or removed if they refer to larger entities which the language in question is actually a part of. ]==] function Language:getOtherNames() -- To be eventually removed, once there are no more uses of the `other_names` field. self:loadInExtraData() return require(language_like_module).getOtherNames(self) end --[==[ Return a combined table of the canonical name, aliases, varieties and other names of a given language. ]==] function Language:getAllNames() self:loadInExtraData() return require(language_like_module).getAllNames(self) end --[==[ Return a table of types as a set (with the types as keys). The possible types are * {language}: This is a language, either full or etymology-only. * {full}: This is a "full" (not etymology-only) language, i.e. the union of {regular}, {reconstructed} and {appendix-constructed}. Note that the types {full} and {etymology-only} also exist for families, so if you want to check specifically for a full language and you have an object that might be a family, you should use {hasType("language", "full")} and not simply {hasType("full")}. * {etymology-only}: This is an etymology-only (not full) language, whose parent is another etymology-only language or a full language. Note that the types {full} and {etymology-only} also exist for families, so if you want to check specifically for an etymology-only language and you have an object that might be a family, you should use {hasType("language", "etymology-only")} and not simply {hasType("etymology-only")}. * {regular}: This indicates a full language that is attested according to [[WT:CFI]] and therefore permitted in the main namespace. There may also be reconstructed terms for the language, which are placed in the {Reconstruction} namespace and must be prefixed with `*` to indicate a reconstruction. Most full languages are natural (not constructed) languages, but a few constructed languages (e.g. Esperanto and Volapük, among others) are also allowed in the mainspace and considered regular languages. * {reconstructed}: This language is not attested according to [[WT:CFI]], and therefore is allowed only in the {Reconstruction} namespace. All terms in this language are reconstructed, and must be prefixed with `*`. Languages such as Proto-Indo-European and Proto-Germanic are in this category. '''Exception:''' Reconstructed languages can have entries in the mainspace using the ''anti-asterisk'' feature. This requires that both the headword for the mainspace term (as specified using {{para|head}}) and links to the term are prefixed with the anti-asterisk indicator `!!`. * {appendix-constructed}: This language is attested but does not meet the additional requirements set out for constructed languages ([[WT:CFI#Constructed languages]]). Its entries must therefore be in the Appendix namespace, but they are not reconstructed and therefore should not have * prefixed in links. ]==] function Language:getTypes() local types = self._types if types == nil then types = {language = true} if self:getFullCode() == self._code then types.full = true else types["etymology-only"] = true end for t in gmatch(self._data.type, "[^,]+") do types[t] = true end self._types = types end return types end --[==[ Given a list of types as strings, return {true} if the language has all of them. ]==] function Language:hasType(...) Language.hasType = require(language_like_module).hasType return self:hasType(...) end --[==[ Return a list containing `WikimediaLanguage` objects (see [[Module:wikimedia languages]]), which represent languages and their codes as they are used in Wikimedia projects for interwiki linking and such. More than one object may be returned, as a single Wiktionary language may correspond to multiple Wikimedia languages. For example, Wiktionary's single code `sh` (Serbo-Croatian) maps to four Wikimedia codes: `sh` (Serbo-Croatian), `bs` (Bosnian), `hr` (Croatian) and `sr` (Serbian). The code for the Wikimedia language is retrieved from the `wikimedia_codes` property in the data modules. If that property is not present, the code of the current language is used. If none of the available codes is actually a valid Wikimedia code, an empty list is returned. ]==] function Language:getWikimediaLanguages() local wm_langs = self._wikimediaLanguageObjects if wm_langs == nil then local codes = self:getWikimediaLanguageCodes() wm_langs = {} for i = 1, #codes do wm_langs[i] = get_wikimedia_lang(codes[i]) end self._wikimediaLanguageObjects = wm_langs end return wm_langs end function Language:getWikimediaLanguageCodes() local wm_langs = self._wikimediaLanguageCodes if wm_langs == nil then wm_langs = self._data.wikimedia_codes if wm_langs then wm_langs = split(wm_langs, ",", true, true) else local code = self._code if is_known_language_tag(code) then wm_langs = {code} else -- Inherit, but only if no codes are specified in the data *and* -- the language code isn't a valid Wikimedia language code. local parent = self:getParent() wm_langs = parent and parent:getWikimediaLanguageCodes() or {} end end self._wikimediaLanguageCodes = wm_langs end return wm_langs end --[==[ Return the name of the Wikipedia article for the language. `project` specifies the language and project to retrieve the article from, defaulting to {"enwiki"} for the English Wikipedia. Normally if specified it should be the project code for a specific-language Wikipedia e.g. "zhwiki" for the Chinese Wikipedia, but it can be any project, including non-Wikipedia ones. If the project is the English Wikipedia and the property {wikipedia_article} is present in the data module it will be used first. In all other cases, a sitelink will be generated from {:getWikidataItem} (if set). The resulting value (or lack of value) is cached so that subsequent calls are fast. If no value could be determined, and `noCategoryFallback` is {false}, `##Language:getCategoryName` is used as fallback; otherwise, {nil} is returned. Note that if `noCategoryFallback` is {nil} or omitted, it defaults to {false} if the project is the English Wikipedia, otherwise to {true}. In other words, under normal circumstances, if the English Wikipedia article couldn't be retrieved, the return value will fall back to a link to the language's category, but this won't normally happen for any other project. ]==] function Language:getWikipediaArticle(noCategoryFallback, project) Language.getWikipediaArticle = require(language_like_module).getWikipediaArticle return self:getWikipediaArticle(noCategoryFallback, project) end function Language:makeWikipediaLink() return make_link(self, "w:" .. self:getWikipediaArticle(), self:getCanonicalName()) end --[==[ Return the name of the Wikimedia Commons category page for the language. ]==] function Language:getCommonsCategory() Language.getCommonsCategory = require(language_like_module).getCommonsCategory return self:getCommonsCategory() end --[==[ Return the Wikidata item ID for the language or {nil}. This corresponds to the the second field in the data modules. ]==] function Language:getWikidataItem() Language.getWikidataItem = require(language_like_module).getWikidataItem return self:getWikidataItem() end --[==[ Return a list of `Script` objects for all scripts that the language is written in. See [[Module:scripts]]. ]==] function Language:getScripts() local scripts = self._scriptObjects if scripts == nil then local codes = self:getScriptCodes() if codes[1] == "All" then scripts = load_data(scripts_data_module) else scripts = {} for i = 1, #codes do scripts[i] = get_script(codes[i]) end end self._scriptObjects = scripts end return scripts end --[==[ Return the list of script codes in the language's data file. ]==] function Language:getScriptCodes() local scripts = self._scriptCodes if scripts == nil then scripts = self._data[4] if scripts then local codes, n = {}, 0 for code in gmatch(scripts, "[^,]+") do n = n + 1 -- Special handling of "Hants", which represents "Hani", "Hant" and "Hans" collectively. if code == "Hants" then codes[n] = "Hani" codes[n + 1] = "Hant" codes[n + 2] = "Hans" n = n + 2 else codes[n] = code end end scripts = codes else scripts = {"None"} end self._scriptCodes = scripts end return scripts end --[==[ Given some text, iterate through the scripts of a given language trying to find the script that best matches the text. Return a {Script} object representing the script. If no match is found at all, it returns the {None} script object. ]==] function Language:findBestScript(text, forceDetect) if not text or text == "" or text == "-" then return get_script("None") end -- Differs from table returned by getScriptCodes, as Hants is not normalized into its constituents. local codes = self._bestScriptCodes if codes == nil then codes = self._data[4] codes = codes and split(codes, ",", true, true) or {"None"} self._bestScriptCodes = codes end local first_sc = codes[1] if first_sc == "All" then return find_best_script_without_lang(text) end local codes_len = #codes if not (forceDetect or first_sc == "Hants" or codes_len > 1) then first_sc = get_script(first_sc) local charset = first_sc.characters return charset and umatch(text, "[" .. charset .. "]") and first_sc or get_script("None") end -- Remove all formatting characters. text = get_plaintext(text) -- Remove all spaces and any ASCII punctuation. Some non-ASCII punctuation is script-specific, so can't be removed. text = ugsub(text, "[%s!\"#%%&'()*,%-./:;?@[\\%]_{}]+", "") if #text == 0 then return get_script("None") end -- Try to match every script against the text, -- and return the one with the most matching characters. local bestcount, bestscript, length = 0 for i = 1, codes_len do local sc = codes[i] -- Special case for "Hants", which is a special code that represents whichever of "Hant" or "Hans" best matches, -- or "Hani" if they match equally. This avoids having to list all three. In addition, "Hants" will be treated -- as the best match if there is at least one matching character, under the assumption that a Han script is -- desirable in terms that contain a mix of Han and other scripts (not counting those which use Jpan or Kore). if sc == "Hants" then local Hani = get_script("Hani") if not Hant_chars then Hant_chars = load_data("Module:zh/data/ts") Hans_chars = load_data("Module:zh/data/st") end local t, s, found = 0, 0 -- This is faster than using mw.ustring.gmatch directly. for ch in gmatch((ugsub(text, "[" .. Hani.characters .. "]", "\255%0")), "\255(.[\128-\191]*)") do found = true if Hant_chars[ch] then t = t + 1 if Hans_chars[ch] then s = s + 1 end elseif Hans_chars[ch] then s = s + 1 else t, s = t + 1, s + 1 end end if found then if t == s then return Hani end return get_script(t > s and "Hant" or "Hans") end else sc = get_script(sc) if not length then length = ulen(text) end -- Count characters by removing everything in the script's charset and comparing to the original length. local charset = sc.characters local count = charset and length - ulen((ugsub(text, "[" .. charset .. "]+", ""))) or 0 if count >= length then return sc elseif count > bestcount then bestcount = count bestscript = sc end end end -- Return best matching script, or otherwise None. return bestscript or get_script("None") end --[==[ Return a `Family` object for the language family that the language belongs to. See [[Module:families]]. ]==] function Language:getFamily() local family = self._familyObject if family == nil then family = self:getFamilyCode() -- If the value is nil, it's cached as false. family = family and get_family(family) or false self._familyObject = family end return family or nil end --[==[ Return the family code in the language's data file. ]==] function Language:getFamilyCode() local family = self._familyCode if family == nil then -- If the value is nil, it's cached as false. family = self._data[3] or false self._familyCode = family end return family or nil end function Language:getFamilyName() local family = self._familyName if family == nil then family = self:getFamily() -- If the value is nil, it's cached as false. family = family and family:getCanonicalName() or false self._familyName = family end return family or nil end do local function check_family(self, family) if type(family) == "table" then family = family:getCode() end if self:getFamilyCode() == family then return true end local self_family = self:getFamily() if self_family:inFamily(family) then return true -- If the family isn't a real family (e.g. creoles) check any ancestors. elseif self_family:inFamily("qfa-not") then local ancestors = self:getAncestors() for _, ancestor in ipairs(ancestors) do if ancestor:inFamily(family) then return true end end end end --[==[ Check whether the language belongs to `family` (which can be a family code or object). A list of objects can be given in place of `family`; in that case, return true if the language belongs to any of the specified families. Note that some languages (in particular, certain creoles) can have multiple immediate ancestors potentially belonging to different families; in that case, return true if the language belongs to any of the specified families. ]==] function Language:inFamily(...) if self:getFamilyCode() == nil then return false end return check_inputs(self, check_family, false, ...) end end function Language:getParent() local parent = self._parentObject if parent == nil then parent = self:getParentCode() -- If the value is nil, it's cached as false. parent = parent and get_by_code(parent, nil, true, true) or false self._parentObject = parent end return parent or nil end function Language:getParentCode() local parent = self._parentCode if parent == nil then -- If the value is nil, it's cached as false. parent = self._data.parent or false self._parentCode = parent end return parent or nil end function Language:getParentName() local parent = self._parentName if parent == nil then parent = self:getParent() -- If the value is nil, it's cached as false. parent = parent and parent:getCanonicalName() or false self._parentName = parent end return parent or nil end function Language:getParentChain() local chain = self._parentChain if chain == nil then chain = {} local parent, n = self:getParent(), 0 while parent do n = n + 1 chain[n] = parent parent = parent:getParent() end self._parentChain = chain end return chain end --[==[ Return a list of the pseudo-families (if any) that a language belongs to. The returned value is a list of Family objects. Pseudo-families are groupings of languages that are not clades but share some geographic, ethnic or other characteristic (e.g. [[:Category:Papuan languages]], [[:Category:Jewish languages]]). ]==] function Language:getPseudoFamilies() self:loadInExtraData() return require(language_like_module).getPseudoFamilies(self) end do local function check_lang(self, lang) for _, parent in ipairs(self:getParentChain()) do if (type(lang) == "string" and lang or lang:getCode()) == parent:getCode() then return true end end end function Language:hasParent(...) return check_inputs(self, check_lang, false, ...) end end --[==[ If the language is etymology-only, iterate through parents until a full language or family is found, and the corresponding object is returned. If the language is a full language, return that language. ]==] function Language:getFull() local full = self._fullObject if full == nil then full = self:getFullCode() full = full == self._code and self or get_by_code(full) self._fullObject = full end return full end --[==[ If the language is an etymology-only language, iterate through parents until a full language or family is found, and the corresponding code is returned. If the language is a full language, return that language's code. ]==] function Language:getFullCode() return self._fullCode or self._code end --[==[ If the language is an etymology-only language, iterate through parents until a full language or family is found, and the corresponding canonical name is returned. If the language is a full language, return that language's canonical name. ]==] function Language:getFullName() local full = self._fullName if full == nil then full = self:getFull():getCanonicalName() self._fullName = full end return full end --[==[ Return a list of {Language} objects for all languages that this language is directly descended from. Generally this is only a single language, but creoles, pidgins and mixed languages can have multiple ancestors. ]==] function Language:getAncestors() local ancestors = self._ancestorObjects if ancestors == nil then ancestors = {} local ancestor_codes = self:getAncestorCodes() if #ancestor_codes > 0 then for _, ancestor in ipairs(ancestor_codes) do insert(ancestors, get_by_code(ancestor, nil, true)) end else local fam = self:getFamily() local protoLang = fam and fam:getProtoLanguage() or nil -- For the cases where the current language is the proto-language -- of its family, or an etymology-only language that is ancestral to that -- proto-language, we need to step up a level higher right from the -- start. if protoLang and ( protoLang:getCode() == self._code or (self:hasType("etymology-only") and protoLang:hasAncestor(self)) ) then fam = fam:getFamily() protoLang = fam and fam:getProtoLanguage() or nil end while not protoLang and not (not fam or fam:getCode() == "qfa-not") do fam = fam:getFamily() protoLang = fam and fam:getProtoLanguage() or nil end insert(ancestors, protoLang) end self._ancestorObjects = ancestors end return ancestors end do -- Avoid a language being its own ancestor via class inheritance. We only need to check for this if the language has -- inherited an ancestor table from its parent, because we never want to drop ancestors that have been explicitly -- set in the data. Recursively iterate over ancestors until we either find self or run out. If self is found, -- return true. local function check_ancestor(self, lang) local codes = lang:getAncestorCodes() if not codes then return nil end for i = 1, #codes do local code = codes[i] if code == self._code then return true end local anc = get_by_code(code, nil, true) if check_ancestor(self, anc) then return true end end end --[==[ Return a list of `Language` codes for all languages that this language is directly descended from. Generally this is only a single language, but creoles, pidgins and mixed languages can have multiple ancestors. ]==] function Language:getAncestorCodes() if self._ancestorCodes then return self._ancestorCodes end local data = self._data local codes = data.ancestors if codes == nil then codes = {} self._ancestorCodes = codes return codes end codes = split(codes, ",", true, true) self._ancestorCodes = codes -- If there are no codes or the ancestors weren't inherited data, there's nothing left to check. if #codes == 0 or self:getData(false, "raw").ancestors ~= nil then return codes end local i = 1 while i <= #codes do if check_ancestor(self, self) then remove(codes, i) else i = i + 1 end end return codes end end --[==[ Given a list of language objects or codes, return true if at least one of them is an ancestor. This includes any etymology-only children of that ancestor. If the language's ancestor(s) are etymology-only languages, it will also return true for those language parent(s) (e.g. if Vulgar Latin is the ancestor, it will also return true for its parent, Latin). However, a parent is excluded from this if the ancestor is also ancestral to that parent (e.g. if Classical Persian is the ancestor, Persian would return {false}, because Classical Persian is also ancestral to Persian). ]==] function Language:hasAncestor(...) local function iterateOverAncestorTree(node, func, parent_check) local ancestors = node:getAncestors() local ancestorsParents = {} for _, ancestor in ipairs(ancestors) do -- When checking the parents of the other language, and the ancestor is also a parent, skip to the next -- ancestor, so that we exclude any etymology-only children of that parent that are not directly related -- (see below). local ret = (parent_check or not node:hasParent(ancestor)) and func(ancestor) or iterateOverAncestorTree(ancestor, func, parent_check) if ret then return ret end end -- Check the parents of any ancestors. We don't do this if checking the parents of the other language, so that -- we exclude any etymology-only children of those parents that are not directly related (e.g. if the ancestor -- is Vulgar Latin and we are checking New Latin, we want it to return false because they are on different -- ancestral branches. As such, if we're already checking the parent of New Latin (Latin) we don't want to -- compare it to the parent of the ancestor (Latin), as this would be a false positive; it should be one or the -- other). if not parent_check then return nil end for _, ancestor in ipairs(ancestors) do local ancestorParents = ancestor:getParentChain() for _, ancestorParent in ipairs(ancestorParents) do if ancestorParent:getCode() == self._code or ancestorParent:hasAncestor(ancestor) then break else insert(ancestorsParents, ancestorParent) end end end for _, ancestorParent in ipairs(ancestorsParents) do local ret = func(ancestorParent) if ret then return ret end end end local function do_iteration(otherlang, parent_check) -- otherlang can't be self if (type(otherlang) == "string" and otherlang or otherlang:getCode()) == self._code then return false end repeat if iterateOverAncestorTree( self, function(ancestor) return ancestor:getCode() == (type(otherlang) == "string" and otherlang or otherlang:getCode()) end, parent_check ) then return true elseif type(otherlang) == "string" then otherlang = get_by_code(otherlang, nil, true) end otherlang = otherlang:getParent() parent_check = false until not otherlang end local parent_check = true for _, otherlang in ipairs{...} do local ret = do_iteration(otherlang, parent_check) if ret then return true end end return false end do local function construct_node(lang, memo) local branch, ancestors = {lang = lang:getCode()} memo[lang:getCode()] = branch for _, ancestor in ipairs(lang:getAncestors()) do if ancestors == nil then ancestors = {} end insert(ancestors, memo[ancestor:getCode()] or construct_node(ancestor, memo)) end branch.ancestors = ancestors return branch end function Language:getAncestorChain() local chain = self._ancestorChain if chain == nil then chain = construct_node(self, {}) self._ancestorChain = chain end return chain end end function Language:getAncestorChainOld() local chain = self._ancestorChain if chain == nil then chain = {} local step = self while true do local ancestors = step:getAncestors() step = #ancestors == 1 and ancestors[1] or nil if not step then break end insert(chain, step) end self._ancestorChain = chain end return chain end local function fetch_descendants(self, fmt) local descendants, family = {}, self:getFamily() -- Iterate over all three datasets. for _, data in ipairs{ require("Module:languages/code to canonical name"), require("Module:etymology languages/code to canonical name"), require("Module:families/code to canonical name"), } do for code in pairs(data) do local lang = get_by_code(code, nil, true, true) if not lang then error(("Internal error: code '%s' can't be fetched"):format(code)) end -- Test for a descendant. Earlier tests weed out most candidates, while the more intensive tests are only used sparingly. if ( code ~= self._code and -- Not self. lang:inFamily(family) and -- In the same family. ( family:getProtoLanguageCode() == self._code or -- Self is the protolanguage. self:hasDescendant(lang) or -- Full hasDescendant check. (lang:getFullCode() == self._code and not self:hasAncestor(lang)) -- Etymology-only child which isn't an ancestor. ) ) then if fmt == "object" then insert(descendants, lang) elseif fmt == "code" then insert(descendants, code) elseif fmt == "name" then insert(descendants, lang:getCanonicalName()) end end end end return descendants end function Language:getDescendants() local descendants = self._descendantObjects if descendants == nil then descendants = fetch_descendants(self, "object") self._descendantObjects = descendants end return descendants end function Language:getDescendantCodes() local descendants = self._descendantCodes if descendants == nil then descendants = fetch_descendants(self, "code") self._descendantCodes = descendants end return descendants end function Language:getDescendantNames() local descendants = self._descendantNames if descendants == nil then descendants = fetch_descendants(self, "name") self._descendantNames = descendants end return descendants end do local function check_lang(self, lang) if type(lang) == "string" then lang = get_by_code(lang, nil, true) end if lang:hasAncestor(self) then return true end end function Language:hasDescendant(...) return check_inputs(self, check_lang, false, ...) end end local function fetch_children(self, fmt) local m_etym_data = require(etymology_languages_data_module) local self_code, children = self._code, {} for code, lang in pairs(m_etym_data) do local _lang = lang repeat local parent = _lang.parent if parent == self_code then if fmt == "object" then insert(children, get_by_code(code, nil, true)) elseif fmt == "code" then insert(children, code) elseif fmt == "name" then insert(children, lang[1]) end break end _lang = m_etym_data[parent] until not _lang end return children end function Language:getChildren() local children = self._childObjects if children == nil then children = fetch_children(self, "object") self._childObjects = children end return children end function Language:getChildrenCodes() local children = self._childCodes if children == nil then children = fetch_children(self, "code") self._childCodes = children end return children end function Language:getChildrenNames() local children = self._childNames if children == nil then children = fetch_children(self, "name") self._childNames = children end return children end function Language:hasChild(...) local lang = ... if not lang then return false elseif type(lang) == "string" then lang = get_by_code(lang, nil, true) end if lang:hasParent(self) then return true end return self:hasChild(select(2, ...)) end --[==[ Return the name of the main category of that language. Example: {French language"} for French, whose category is at [[:Category:French language]]. Unless optional argument `nocap` is given, the language name at the beginning of the returned value will be capitalized. This capitalization is correct for category names, but not if the language name is lowercase and the returned value of this function is used in the middle of a sentence. ]==] function Language:getCategoryName(nocap) local name = self._categoryName if name == nil then name = self:getCanonicalName() -- If a substrate, omit any leading article. if self:getFamilyCode() == "qfa-sub" then name = name:gsub("^the ", ""):gsub("^a ", "") end -- Only add " language" if a full language. if self:hasType("full") then -- Unless the canonical name already ends with "language", "lect" or their derivatives, add " language". if not (match(name, "[Ll]anguage$") or match(name, "[Ll]ect$")) then name = name .. " language" end end self._categoryName = name end if nocap then return name end return mw.getContentLanguage():ucfirst(name) end --[==[ Create a link to the category; the link text is the canonical name. ]==] function Language:makeCategoryLink() return make_link(self, ":Category:" .. self:getCategoryName(), self:getDisplayForm()) end function Language:getStandardCharacters(sc) local standard_chars = self._data.standard_chars if type(standard_chars) ~= "table" then return standard_chars elseif sc and type(sc) ~= "string" then check_object("script", nil, sc) sc = sc:getCode() end if (not sc) or sc == "None" then local scripts = {} for _, script in pairs(standard_chars) do insert(scripts, script) end return concat(scripts) end if standard_chars[sc] then return standard_chars[sc] .. (standard_chars[1] or "") end end --[==[ Strip diacritics from display text `text` (in a language-specific fashion), which is in the script `sc`. If `sc` is omitted or {nil}, the script is autodetected. This also strips certain punctuation characters from the end and (in the case of Spanish upside-down question mark and exclamation points) from the beginning; strips any whitespace at the end of the text or between the text and final stripped punctuation characters; and applies some language-specific Unicode normalizations to replace discouraged characters with their prescribed alternatives. Return the stripped text. ]==] function Language:stripDiacritics(text, sc) if (not text) or text == "" then return text end sc = checkScript(text, self, sc) text = normalize(text, sc) -- FIXME, rename makeEntryName to stripDiacritics and get rid of second and third return values -- everywhere local _ text, _, _ = iterateSectionSubstitutions(self, text, sc, nil, nil, self._data.strip_diacritics or self._data.entry_name, "strip_diacritics", "stripDiacritics") text = umatch(text, "^[¿¡]?(.-[^%s%p].-)%s*[؟?!;՛՜ ՞ ՟?!︖︕।॥။၊་།]?$") or text return text end --[==[ Convert a ''logical'' pagename (the pagename as it appears to the user, after diacritics and punctuation have been stripped) to a ''physical'' pagename (the pagename as it appears in the MediaWiki database). Reasons for a difference between the two are (a) unsupported titles such as `[ ]` (with square brackets in them), `#` (pound/hash sign) and `¯\_(ツ)_/¯` (with underscores), as well as overly long titles of various sorts; (b) "mammoth" pages that are split into parts (e.g. `a`, which is split into physical pagenames `a/languages A to L` and `a/languages M to Z`). For almost all purposes, you should work with logical and not physical pagenames. But there are certain use cases that require physical pagenames, such as checking the existence of a page or retrieving a page's contents. `pagename` is the logical pagename to be converted. `is_reconstructed_or_appendix` indicates whether the page is in the `Reconstruction` or `Appendix` namespaces. If it is omitted or has the value {nil}, the pagename is checked for an initial asterisk, and if found, the page is assumed to be a `Reconstruction` page. Setting a value of `false` or `true` to `is_reconstructed_or_appendix` disables this check and allows for mainspace pagenames that begin with an asterisk. ]==] function Language:logicalToPhysical(pagename, is_reconstructed_or_appendix) -- FIXME: This probably shouldn't happen but it happens when makeEntryName() receives nil. if pagename == nil then track("nil-passed-to-logicalToPhysical") return nil end local initial_asterisk if is_reconstructed_or_appendix == nil then local pagename_minus_initial_asterisk initial_asterisk, pagename_minus_initial_asterisk = pagename:match("^(%*)(.*)$") if pagename_minus_initial_asterisk then is_reconstructed_or_appendix = true pagename = pagename_minus_initial_asterisk elseif self:hasType("appendix-constructed") then is_reconstructed_or_appendix = true end end if not is_reconstructed_or_appendix then -- Check if the pagename is a listed unsupported title. local unsupportedTitles = load_data(links_data_module).unsupported_titles if unsupportedTitles[pagename] then return "Unsupported titles/" .. unsupportedTitles[pagename] end end -- Set `unsupported` as true if certain conditions are met. local unsupported -- Check if there's an unsupported character. \239\191\189 is the replacement character U+FFFD, which can't be typed -- directly here due to an abuse filter. Unix-style dot-slash notation is also unsupported, as it is used for -- relative paths in links, as are 3 or more consecutive tildes. Note: match is faster with magic -- characters/charsets; find is faster with plaintext. if ( match(pagename, "[#<>%[%]_{|}]") or find(pagename, "\239\191\189") or match(pagename, "%f[^%z/]%.%.?%f[%z/]") or find(pagename, "~~~") ) then unsupported = true -- If it looks like an interwiki link. elseif find(pagename, ":") then local prefix = gsub(pagename, "^:*(.-):.*", ulower) if ( load_data("Module:data/namespaces")[prefix] or load_data("Module:data/interwikis")[prefix] ) then unsupported = true end end -- Escape unsupported characters so they can be used in titles. ` is used as a delimiter for this, so a raw use of -- it in an unsupported title is also escaped here to prevent interference; this is only done with unsupported -- titles, though, so inclusion won't in itself mean a title is treated as unsupported (which is why it's excluded -- from the earlier test). if unsupported then -- FIXME: This conversion needs to be different for reconstructed pages with unsupported characters. There -- aren't any currently, but if there ever are, we need to fix this e.g. to put them in something like -- Reconstruction:Proto-Indo-European/Unsupported titles/`lowbar``num`. local unsupported_characters = load_data(links_data_module).unsupported_characters pagename = pagename:gsub("[#<>%[%]_`{|}\239]\191?\189?", unsupported_characters) :gsub("%f[^%z/]%.%.?%f[%z/]", function(m) return (gsub(m, "%.", "`period`")) end) :gsub("~~~+", function(m) return (gsub(m, "~", "`tilde`")) end) pagename = "Unsupported titles/" .. pagename elseif not is_reconstructed_or_appendix then -- Check if this is a mammoth page. If so, which subpage should we link to? local m_links_data = load_data(links_data_module) local mammoth_page_type = m_links_data.mammoth_pages[pagename] if mammoth_page_type then local canonical_name = self:getFullName() if canonical_name ~= "Translingual" and canonical_name ~= "English" then local this_subpage local L2_sort_key = get_L2_sort_key(canonical_name) for _, subpage_spec in ipairs(m_links_data.mammoth_page_subpage_types[mammoth_page_type]) do -- unpack() fails utterly on data loaded using mw.loadData() even if offsets are given local subpage, pattern = subpage_spec[1], subpage_spec[2] if pattern == true or L2_sort_key:match(pattern) then this_subpage = subpage break end end if not this_subpage then error(("Internal error: Bad data in mammoth_page_subpage_pages in [[Module:links/data]] for mammoth page %s, type %s; last entry didn't have 'true' in it"):format( pagename, mammoth_page_type)) end pagename = pagename .. "/" .. this_subpage end end end return (initial_asterisk or "") .. pagename end --[==[ Strip the diacritics from a display pagename and convert the resulting logical pagename into a physical pagename. This allows you, for example, to retrieve the contents of the page or check its existence. WARNING: This is deprecated and will be going away. It is a simple composition of `##Language:stripDiacritics` and `##Language:logicalToPhysical`; most callers only want the former, and if you need both, call them both yourself. `text` and `sc` are as in `##Language:stripDiacritics`, and `is_reconstructed_or_appendix` is as in `##Language:logicalToPhysical`. ]==] function Language:makeEntryName(text, sc, is_reconstructed_or_appendix) track("makeEntryName called") return self:logicalToPhysical(self:stripDiacritics(text, sc), is_reconstructed_or_appendix) end --[==[ Generate term alternants (e.g. simplified versions of traditional Chinese terms) using a language-specific method, and return them as a table. If the language provides no method for generating alternants, return a table containing only the input term. ]==] function Language:generateAlternants(text, sc) local generate_alternants = self._data.generate_alternants if generate_alternants == nil then return {text} end sc = checkScript(text, self, sc) return require("Module:" .. generate_alternants).generateAlternants(text, self, sc) end --[==[ Create a sort key for the given stripped text, following the rules appropriate for the language. This removes diacritical marks from the stripped text if they are not considered significant for sorting, and may perform some other changes. Any initial hyphen is also removed, and anything in parentheses is removed as well. The `sort_key` setting for each language in the data modules defines the replacements made by this function, or it gives the name of the module that takes the stripped text and returns a sortkey. ]==] function Language:makeSortKey(text, sc) if (not text) or text == "" then return text end if match(text, "<[^<>]+>") then track("track HTML tag") end -- Remove directionality/control characters, bold, italics, soft hyphens, strip markers and HTML tags. -- FIXME: Partly duplicated with remove_formatting() in [[Module:links]]. text = ugsub(text, "[\194\173\216\156\226\128\142\226\128\143\226\128\170-\226\128\174\226\129\166-\226\129\175]", "") text = text:gsub("('*)'''(.-'*)'''", "%1%2"):gsub("('*)''(.-'*)''", "%1%2") text = gsub(unstrip(text), "<[^<>]+>", "") text = decode_uri(text, "PATH") text = checkNoEntities(text) -- Remove initial hyphens and * unless the term only consists of spacing + punctuation characters. text = ugsub(text, "^([􀀀-􏿽]*)[-־ـ᠊*]+([􀀀-􏿽]*)(.*[^%s%p].*)", "%1%2%3") sc = checkScript(text, self, sc) text = normalize(text, sc) text = removeCarets(text, sc) -- For languages with dotted dotless i, ensure that "İ" is sorted as "i", and "I" is sorted as "ı". if self:hasDottedDotlessI() then text = gsub(text, "I\204\135", "i") -- decomposed "İ" :gsub("I", "ı") text = sc:toFixedNFD(text) end -- Convert to lowercase, make the sortkey, then convert to uppercase. Where the language has dotted dotless i, it is -- usually not necessary to convert "i" to "İ" and "ı" to "I" first, because "I" will always be interpreted as -- conventional "I" (not dotless "İ") by any sorting algorithms, which will have been taken into account by the -- sortkey substitutions themselves. However, if no sortkey substitutions have been specified, then conversion is -- necessary so as to prevent "i" and "ı" both being sorted as "I". -- -- An exception is made for scripts that (sometimes) sort by scraping page content, as that means they are sensitive -- to changes in capitalization (as it changes the target page). if not sc:sortByScraping() then text = ulower(text) end local actual_substitution_data, _ -- Don't trim whitespace here because it's significant at the beginning of a sort key or sort base. text, _, actual_substitution_data = iterateSectionSubstitutions(self, text, sc, nil, nil, self._data.sort_key, "sort_key", "makeSortKey", "notrim") if not sc:sortByScraping() then if self:hasDottedDotlessI() and not actual_substitution_data then text = text:gsub("ı", "I"):gsub("i", "İ") text = sc:toFixedNFC(text) end text = uupper(text) end -- Remove parentheses, as long as they are either preceded or followed by something. text = gsub(text, "(.)[()]+", "%1"):gsub("[()]+(.)", "%1") text = escape_risky_characters(text) return text end --[==[ Create the form used as as a basis for display text and transliteration. FIXME: Rename to correctInputText(). ]==] local function processDisplayText(text, self, sc, keepCarets, keepPrefixes) local subbedChars = {} text, subbedChars = doTempSubstitutions(text, subbedChars, keepCarets) text = decode_uri(text, "PATH") text = checkNoEntities(text) sc = checkScript(text, self, sc) text = normalize(text, sc) text, subbedChars = iterateSectionSubstitutions(self, text, sc, subbedChars, keepCarets, self._data.display_text, "display_text", "makeDisplayText") text = removeCarets(text, sc) -- Remove any interwiki link prefixes (unless they have been escaped or this has been disabled). if find(text, ":") and not keepPrefixes then local rep repeat text, rep = gsub(text, "\\\\(\\*:)", "\3%1") until rep == 0 text = gsub(text, "\\:", "\4") while true do local prefix = gsub(text, "^(.-):.+", function(m1) return (gsub(m1, "\244[\128-\191]*", "")) end) -- Check if the prefix is an interwiki, though ignore capitalised Wiktionary:, which is a namespace. if not prefix or prefix == text or prefix == "Wiktionary" or not (load_data("Module:data/interwikis")[ulower(prefix)] or prefix == "") then break end text = gsub(text, "^(.-):(.*)", function(m1, m2) local ret = {} for subbedChar in gmatch(m1, "\244[\128-\191]*") do insert(ret, subbedChar) end return concat(ret) .. m2 end) end text = gsub(text, "\3", "\\"):gsub("\4", ":") end return text, subbedChars end --[==[ Make the display text (i.e. what is displayed on the page). ]==] function Language:makeDisplayText(text, sc, keepPrefixes) if not text or text == "" then return text end local subbedChars text, subbedChars = processDisplayText(text, self, sc, nil, keepPrefixes) text = escape_risky_characters(text) return undoTempSubstitutions(text, subbedChars) end --[==[ Transliterate the text from the given script into Latin script (see [[Wiktionary:Transliteration and romanization]]). The language must have the `translit` property for this to work; if it is not present, {nil} is returned. The `sc` parameter is handled by the transliteration module, and how it is handled is specific to that module. Some transliteration modules may tolerate {nil} as the script, others require it to be one of the possible scripts that the module can transliterate, and will throw an error if it's not one of them. For this reason, the `sc` parameter should always be provided when writing non-language-specific code. The `module_override` parameter is used to override the default module that is used to provide the transliteration. This is useful in cases where you need to demonstrate a particular module in use, but there is no default module yet, or you want to demonstrate an alternative version of a transliteration module before making it official. It should not be used in real modules or templates, only for testing. All uses of this parameter are tracked by [[Wiktionary:Tracking/languages/module_override]]. '''Known bugs''': * This function assumes {tr(s1) .. tr(s2) == tr(s1 .. s2)}. When this assertion fails, wikitext markups like <nowiki>'''</nowiki> can cause wrong transliterations. * HTML entities like `&amp;apos;`, often used to escape wikitext markups, do not work. ]==] function Language:transliterate(text, sc, module_override) -- If there is no text, or the language doesn't have transliteration data and there's no override, return nil. if not text or text == "" or text == "-" then return text end -- If the script is not transliteratable (and no override is given), return nil. sc = checkScript(text, self, sc) if not (sc:isTransliterated() or module_override) then -- temporary tracking to see if/when this gets triggered track("non-transliterable") track("non-transliterable/" .. self._code) track("non-transliterable/" .. sc:getCode()) track("non-transliterable/" .. sc:getCode() .. "/" .. self._code) return nil end -- Remove any strip markers. text = unstrip(text) -- Do not process the formatting into PUA characters for certain languages. local processed = load_data(languages_data_module).substitution[self._code] ~= "none" -- Get the display text with the keepCarets flag set. local subbedChars if processed then text, subbedChars = processDisplayText(text, self, sc, true) end -- Transliterate (using the module override if applicable). text, subbedChars = iterateSectionSubstitutions(self, text, sc, subbedChars, true, module_override or self._data.translit, "translit", "tr") if not text then return nil end -- Incomplete transliterations return nil. local charset = sc.characters if charset and umatch(text, "[" .. charset .. "]") then -- Remove any characters in Latin, which includes Latin characters also included in other scripts (as these are -- false positives), as well as any PUA substitutions. Anything remaining should only be script code "None" -- (e.g. numerals). local check_text = ugsub(text, "[" .. get_script("Latn").characters .. "􀀀-􏿽]+", "") -- Set none_is_last_resort_only flag, so that any non-None chars will cause a script other than "None" to be -- returned. if find_best_script_without_lang(check_text, true):getCode() ~= "None" then return nil end end if processed then text = escape_risky_characters(text) text = undoTempSubstitutions(text, subbedChars) end -- If allowed for this script, capitalize transliteration letters preceded by ^ and remove the caret. if text and text:find("^", 1, true) then assertCaretCapitalizationAllowed(text, sc) if sc:hasCaretCapitalization() then text = processCarets(text, "%^([\128-\191\244]*%*?)([^\128-\191\244][\128-\191]*)", function(m1, m2) return m1 .. uupper(m2) end) end end -- Track module overrides. if module_override ~= nil then track("module_override") end return text end do local function handle_language_spec(self, spec, sc) local ret = self["_" .. spec] if ret == nil then ret = self._data[spec] if type(ret) == "string" then ret = list_to_set(split(ret, ",", true, true)) end self["_" .. spec] = ret end if type(ret) == "table" then ret = ret[sc:getCode()] end return not not ret end function Language:overrideManualTranslit(sc) return handle_language_spec(self, "override_translit", sc) end function Language:link_tr(sc) return handle_language_spec(self, "link_tr", sc) end end --[==[ Return {true} if the language has a transliteration module, or {false} if it doesn't. ]==] function Language:hasTranslit() return not not self._data.translit end --[==[ Return {true} if the language uses the letters I/ı and İ/i, or {false} if it doesn't.]==] function Language:hasDottedDotlessI() return not not self._data.dotted_dotless_i end function Language:toJSON(opts) local strip_diacritics, strip_diacritics_patterns, strip_diacritics_remove_diacritics = self._data.strip_diacritics if strip_diacritics then if strip_diacritics.from then strip_diacritics_patterns = {} for i, from in ipairs(strip_diacritics.from) do insert(strip_diacritics_patterns, {from = from, to = strip_diacritics.to[i] or ""}) end end strip_diacritics_remove_diacritics = strip_diacritics.remove_diacritics end -- mainCode should only end up non-nil if dontCanonicalizeAliases is passed to make_object(). -- props should either contain zero-argument functions to compute the value, or the value itself. local props = { ancestors = function() return self:getAncestorCodes() end, canonicalName = function() return self:getCanonicalName() end, categoryName = function() return self:getCategoryName("nocap") end, code = self._code, mainCode = self._mainCode, parent = function() return self:getParentCode() end, full = function() return self:getFullCode() end, stripDiacriticsPatterns = strip_diacritics_patterns, stripDiacriticsRemoveDiacritics = strip_diacritics_remove_diacritics, family = function() return self:getFamilyCode() end, aliases = function() return self:getAliases() end, varieties = function() return self:getVarieties() end, otherNames = function() return self:getOtherNames() end, pseudoFamilies = function() return self:getPseudoFamilies() end, scripts = function() return self:getScriptCodes() end, type = function() return keys_to_list(self:getTypes()) end, wikimediaLanguages = function() return self:getWikimediaLanguageCodes() end, wikidataItem = function() return self:getWikidataItem() end, wikipediaArticle = function() return self:getWikipediaArticle(true) end, } local ret = {} for prop, val in pairs(props) do if not opts.skip_fields or not opts.skip_fields[prop] then if type(val) == "function" then ret[prop] = val() else ret[prop] = val end end end -- Use `deep_copy` when returning a table, so that there are no editing restrictions imposed by `mw.loadData`. return opts and opts.lua_table and deep_copy(ret) or to_json(ret, opts) end function export.getDataModuleName(code) local letter = match(code, "^(%l)%l%l?$") return "Module:" .. ( letter == nil and "languages/data/exceptional" or #code == 2 and "tungumál/gögn/2" or "languages/data/3/" .. letter ) end get_data_module_name = export.getDataModuleName function export.getExtraDataModuleName(code) return get_data_module_name(code) .. "/extra" end get_extra_data_module_name = export.getExtraDataModuleName do local function make_stack(data) local key_types = { [2] = "unique", aliases = "unique", other_names = "unique", type = "append", varieties = "unique", wikipedia_article = "unique", wikimedia_codes = "unique" } local function __index(self, k) local stack, key_type = getmetatable(self), key_types[k] -- Data that isn't inherited from the parent. if key_type == "unique" then local v = stack[stack[make_stack]][k] if v == nil then local layer = stack[0] if layer then -- Could be false if there's no extra data. v = layer[k] end end return v -- Data that is appended by each generation. elseif key_type == "append" then local parts, offset, n = {}, 0, stack[make_stack] for i = 1, n do local part = stack[i][k] if part == nil then offset = offset + 1 else parts[i - offset] = part end end return offset ~= n and concat(parts, ",") or nil end local n = stack[make_stack] while true do local layer = stack[n] if not layer then -- Could be false if there's no extra data. return nil end local v = layer[k] if v ~= nil then return v end n = n - 1 end end local function __newindex() error("table is read-only") end local function __pairs(self) -- Iterate down the stack, caching keys to avoid duplicate returns. local stack, seen = getmetatable(self), {} local n = stack[make_stack] local iter, state, k, v = pairs(stack[n]) return function() repeat repeat k = iter(state, k) if k == nil then n = n - 1 local layer = stack[n] if not layer then -- Could be false if there's no extra data. return nil end iter, state, k = pairs(layer) end until not (k == nil or seen[k]) -- Get the value via a lookup, as the one returned by the -- iterator will be the raw value from the current layer, -- which may not be the one __index will return for that -- key. Also memoize the key in `seen` (even if the lookup -- returns nil) so that it doesn't get looked up again. -- TODO: store values in `self`, avoiding the need to create -- the `seen` table. The iterator will need to iterate over -- `self` with `next` first to find these on future loops. v, seen[k] = self[k], true until v ~= nil return k, v end end local __ipairs = require(table_module).indexIpairs function make_stack(data) local stack = { data, [make_stack] = 1, -- stores the length and acts as a sentinel to confirm a given metatable is a stack. __index = __index, __newindex = __newindex, __pairs = __pairs, __ipairs = __ipairs, } stack.__metatable = stack return setmetatable({}, stack), stack end return make_stack(data) end local function get_stack(data) local stack = getmetatable(data) return stack and type(stack) == "table" and stack[make_stack] and stack or nil end --[==[ <span style="color: var(--wikt-palette-red,#BA0000)">This function is not for use in entries or other content pages.</span> Return a blob of data about the language. The format of this blob is undocumented, and perhaps unstable; it's intended for things like the module's own unit-tests, which are "close friends" with the module and will be kept up-to-date as the format changes. If `extra` is set, any extra data in the relevant `/extra` module will be included. (Note that it will be included anyway if it has already been loaded into the language object.) If `raw` is set, then the returned data will not contain any data inherited from parent objects. -- Do NOT use these methods! -- All uses should be pre-approved on the talk page! ]==] function Language:getData(extra, raw) if extra then self:loadInExtraData() end local data = self._data -- If raw is not set, just return the data. if not raw then return data end local stack = get_stack(data) -- If there isn't a stack or its length is 1, return the data. Extra data (if any) will be included, as it's -- stored at key 0 and doesn't affect the reported length. if stack == nil then return data end local n = stack[make_stack] if n == 1 then return data end extra = stack[0] -- If there isn't any extra data, return the top layer of the stack. if extra == nil then return stack[n] end -- If there is, return a new stack which has the top layer at key 1 and the extra data at key 0. data, stack = make_stack(stack[n]) stack[0] = extra return data end function Language:loadInExtraData() -- Only full languages have extra data. if not self:hasType("language", "full") then return end local data = self._data -- If there's no stack, create one. local stack = get_stack(self._data) if stack == nil then data, stack = make_stack(data) -- If already loaded, return. elseif stack[0] ~= nil then return end self._data = data -- Load extra data from the relevant module and add it to the stack at key 0, so that the __index and __pairs -- metamethods will pick it up, since they iterate down the stack until they run out of layers. local code = self._code local modulename = get_extra_data_module_name(code) -- No data cached as false. stack[0] = modulename and load_data(modulename)[code] or false end --[==[ Return the name of the module containing the language's data, e.g. [[Module:languages/data/3/k]] for three-letter codes beginning with `k`. ]==] function Language:getDataModuleName() local name = self._dataModuleName if name == nil then name = self:hasType("etymology-only") and etymology_languages_data_module or get_data_module_name(self._mainCode or self._code) self._dataModuleName = name end return name end --[==[ Return the name of the module containing the language's extra data, e.g. [[Module:languages/data/3/k/extra]] for three-letter codes beginning with `k`. ]==] function Language:getExtraDataModuleName() local name = self._extraDataModuleName if name == nil then name = not self:hasType("etymology-only") and get_extra_data_module_name(self._mainCode or self._code) or false self._extraDataModuleName = name end return name or nil end function export.makeObject(code, data, dontCanonicalizeAliases) local data_type = type(data) if data_type ~= "table" then error(("bad argument #2 to 'makeObject' (table expected, got %s)"):format(data_type)) end -- Convert any aliases. local input_code = code code = normalize_code(code) input_code = dontCanonicalizeAliases and input_code or code local parent if data.parent then parent = get_by_code(data.parent, nil, true, true) else parent = Language end parent.__index = parent local lang = {_code = input_code} -- This can only happen if dontCanonicalizeAliases is passed to make_object(). if code ~= input_code then lang._mainCode = code end local parent_data = parent._data if parent_data == nil then -- Full code is the same as the code. lang._fullCode = parent._code or code else -- Copy full code. lang._fullCode = parent._fullCode local stack = get_stack(parent_data) if stack == nil then parent_data, stack = make_stack(parent_data) end -- Insert the input data as the new top layer of the stack. local n = stack[make_stack] + 1 data, stack[n], stack[make_stack] = parent_data, data, n end lang._data = data return setmetatable(lang, parent) end make_object = export.makeObject end --[==[ Find the language whose code matches the one provided. If it exists, return a `Language` object representing the language. Otherwise, return {nil}, unless `paramForError` is given, in which case an error is generated. If `paramForError` is {true}, a generic error message mentioning the bad code is generated; otherwise `paramForError` should be a string or number specifying the parameter that the code came from, and this parameter will be mentioned in the error message along with the bad code. If `allowEtymLang` is specified, etymology-only language codes are allowed and looked up along with full language codes. If `allowFamily` is specified, language family codes are allowed and looked up along with normal language codes. ]==] function export.getByCode(code, paramForError, allowEtymLang, allowFamily) -- [[ Track uses of paramForError, ultimately so it can be removed, as error-handling should be done by -- [[Module:parameters]], not here. ]] -- I (Benwing) disagree; that is an idealistic goal unlikely to be achievable -- realistically. if paramForError ~= nil then track("paramForError") end if type(code) ~= "string" then local typ if not code then typ = "nil" elseif check_object("language", true, code) then typ = "a language object" elseif check_object("family", true, code) then typ = "a family object" else typ = "a " .. type(code) end error("The function getByCode expects a string as its first argument, but received " .. typ .. ".") end local m_data = load_data(languages_data_module) if m_data.aliases[code] or m_data.track[code] then track(code) end local norm_code = normalize_code(code) -- Get the data, checking for etymology-only languages if allowEtymLang is set. local data = load_data(get_data_module_name(norm_code))[norm_code] or allowEtymLang and load_data(etymology_languages_data_module)[norm_code] -- If no data was found and allowFamily is set, check the family data. If the main family data was found, make the -- object with [[Module:families]] instead, as family objects have different methods. However, if it's an -- etymology-only family, use make_object in this module (which handles object inheritance), and the -- family-specific methods will be inherited from the parent object. if data == nil and allowFamily then data = load_data("Module:families/data")[norm_code] if data ~= nil then if data.parent == nil then return make_family_object(norm_code, data) elseif not allowEtymLang then data = nil end end end local retval = code and data and make_object(code, data) if not retval and paramForError then require("Module:languages/errorGetBy").code(code, paramForError, allowEtymLang, allowFamily) end return retval end get_by_code = export.getByCode --[==[ Find the language whose canonical name (the name used to represent that language on Wiktionary) or other name matches the one provided. If it exists, return a `Language` object representing the language. Otherwise, return {nil}, unless `paramForError` is given, in which case an error is generated. If `allowEtymLang` is specified, etymology-only language codes are allowed and looked up along with full language codes. If `allowFamily` is specified, language family codes are allowed and looked up along with normal language codes. The canonical name of languages should always be unique (it is an error for two languages on Wiktionary to share the same canonical name), so this is guaranteed to give at most one result. This function is powered by [[Module:languages/canonical names]], which contains a pre-generated mapping of full-language canonical names to codes. It is generated by going through the [[:Category:Language data modules]] for full languages. When `allowEtymLang` is specified for the above function, [[Module:etymology languages/canonical names]] may also be used, and when `allowFamily` is specified for the above function, [[Module:families/canonical names]] may also be used. ]==] function export.getByCanonicalName(name, errorIfInvalid, allowEtymLang, allowFamily) local byName = load_data("Module:languages/canonical names") local code = byName and byName[name] if not code and allowEtymLang then byName = load_data("Module:etymology languages/canonical names") code = byName and byName[name] or byName[gsub(name, " [Ss]ubstrate$", "")] or byName[gsub(name, "^a ", "")] or byName[gsub(name, "^a ", ""):gsub(" [Ss]ubstrate$", "")] or -- For etymology families like "ira-pro". -- FIXME: This is not ideal, as it allows " languages" to be appended to any etymology-only language, too. byName[match(name, "^(.*) languages$")] end if not code and allowFamily then byName = load_data("Module:families/canonical names") code = byName[name] or byName[match(name, "^(.*) languages$")] end local retval = code and get_by_code(code, errorIfInvalid, allowEtymLang, allowFamily) if not retval and errorIfInvalid then require("Module:languages/errorGetBy").canonicalName(name, allowEtymLang, allowFamily) end return retval end --[==[ Used by [[Module:tungumál/gögn/2]] (et al.) and [[Module:etymology languages/data]], [[Module:families/data]], [[Module:scripts/data]] and [[Module:writing systems/data]] to finalize the data into the format that is actually returned. ]==] function export.finalizeData(data, main_type, variety) local fields = {"type"} if main_type == "language" then insert(fields, 4) -- script codes insert(fields, "ancestors") insert(fields, "link_tr") insert(fields, "override_translit") insert(fields, "wikimedia_codes") elseif main_type == "script" then insert(fields, 3) -- writing system codes end -- Families and writing systems have no extra fields to process. local fields_len = #fields for _, entity in next, data do if variety then -- Move parent from 3 to "parent" and family from "family" to 3. These are different for the sake of -- convenience, since very few varieties have the family specified, whereas all of them have a parent. entity.parent, entity[3] = entity[3], entity.family entity.family = nil -- Give the type "regular" iff not a variety and no other types are assigned. elseif not (entity.type or entity.parent) then entity.type = "regular" end for i = 1, fields_len do local key = fields[i] local field = entity[key] if field and type(field) == "string" then entity[key] = gsub(field, "%s*,%s*", ",") end end end return data end --[==[ For backwards compatibility only; modules should require the error themselves. ]==] function export.err(lang_code, param, code_desc, template_tag, not_real_lang) return require("Module:languages/error")(lang_code, param, code_desc, template_tag, not_real_lang) end return export f7x2gq5qy5om9kg3s8byz1wtrydtp5p Module:tungumál/gögn 828 119300 393948 2026-10-08T19:32:16Z Ow! That Hurts! 8952 skapað 393948 Scribunto text/plain local export = {} -- We can't use mw.loadData() on [[Module:languages/chars]] because [[Module:languages/data]] itself is sometimes loaded -- using mw.loadData(), and calling mw.loadData() on [[Module:languages/chars]] will insert metatables into the -- character tables, which the second mw.loadData() will choke on. local m_chars = require("Module:languages/chars") local u = require("Module:string/char") local c = m_chars.chars export.chars = c local p = m_chars.puaChars export.puaChars = p local cs = m_chars.chars_substitutions export.chars_substitutions = cs -- FIXME! Many of the script-specific values below can be moved to [[Module:scripts/data]] to serve as script-wide -- fallback values instead of specifying them for each language using the script. local s = {} -- These values are placed here to make it possible to synchronise a group of languages without the need for a dedicated function module. -- cau do local cau_remove_diacritics = c.grave .. c.acute .. c.macron local cau_from = {"[IlΙІӀᴴ]"} local cau_to = {{ ["l"] = "ӏ", ["Ι"] = "ӏ", ["І"] = "ӏ", ["Ӏ"] = "ӏ", ["ᴴ"] = "ᵸ", }} s["cau-Cyrl-displaytext"] = { from = cau_from, to = cau_to, } s["cau-Cyrl-stripdiacritics"] = { remove_diacritics = cau_remove_diacritics, from = cau_from, to = cau_to, } s["cau-Latn-stripdiacritics"] = {remove_diacritics = cau_remove_diacritics} end s["itc-Latn-displaytext"] = { from = {c.caron}, to = {c.breve}, } s["itc-Latn-stripdiacritics"] = {remove_diacritics = c.macron .. c.breve .. c.diaer .. c.caron .. c.dinvbreve} s["itc-Latn-sortkey"] = { remove_diacritics = c.circ .. c.tilde .. c.macron .. c.breve .. c.diaer .. c.caron .. c.zigzag .. c.dmacron .. c.dtilde .. c.dinvbreve .. c.small_a .. c.small_e .. c.small_i .. c.small_o .. c.small_u, -- Chiefly medieval abbreviations. from = {"ᵃ", "æ", "[đꝱꟈ]", "ᵉ", "ⁱ", "ꝁ", "[ƚꝉꝲ]", "ꝳ", "ꝴ", "[ꝋᵒ]", "œ", "[ꝑꝓꝕ]", "[ꝗꝙ]", "[ꝛꝵꝶꝝ]", "[ꟊˢ]", "[ꝷᵗ]", "ᵘ", "ꝟ", "⁊"}, to = {"a", "ae", "d", "e", "i", "k", "l", "m", "n", "o", "oe", "p", "q", "r", "s", "t", "u", "v", "&"} } s["Jpan-standardchars"] = -- exclude ぢづヂヅ "ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちっつてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろん" .. "ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチッツテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロン" local jpx_displaytext = { from = {"~", "="}, to = {"〜", "゠"} } s["jpx-displaytext"] = { Jpan = jpx_displaytext, Hani = jpx_displaytext, Hrkt = jpx_displaytext, Hira = jpx_displaytext, Kana = jpx_displaytext -- not Latn or Brai } s["jpx-stripdiacritics"] = s["jpx-displaytext"] s["jpx-sortkey"] = { Jpan = "Jpan-sortkey", Hani = "Hani-sortkey", Hrkt = "Hira-sortkey", -- sort general kana by normalizing to Hira Hira = "Hira-sortkey", Kana = "Kana-sortkey", Latn = {remove_diacritics = c.tilde .. c.macron .. c.diaer} } s["jpx-translit"] = { Hrkt = "Hrkt-translit", Hira = "Hrkt-translit", Kana = "Hrkt-translit" } s["roa-oil-sortkey"] = { remove_diacritics = c.grave .. c.acute .. c.circ .. c.tilde .. c.diaer .. c.ringabove .. c.cedilla .. "'", from = {"æ", "œ", "·"}, to = {"ae", "oe", " "} } s["wen-sortkey"] = { from = {"ch", "[lłßꞩẜ]", "dz[" .. c.caron .. c.acute .. "]", "[bcefmnoprswz][" .. c.caron .. c.acute .. c.dotabove .. "]"}, to = { "h" .. p[1], { ["l"] = "l" .. p[1], ["ł"] = "l", ["ß"] = "s", ["ꞩ"] = "š", ["ẜ"] = "š", }, { ["dz" .. c.caron] = "d" .. p[1], ["dz" .. c.acute] = "d" .. p[2] }, { ["b" .. c.acute] = "b" .. p[1], ["c" .. c.caron] = "c" .. p[1], ["c" .. c.acute] = "c" .. p[2], ["e" .. c.caron] = "e" .. p[1], ["e" .. c.dotabove] = "e" .. p[1], ["f" .. c.acute] = "f" .. p[1], ["m" .. c.acute] = "m" .. p[1], ["n" .. c.acute] = "n" .. p[1], ["o" .. c.acute] = "o" .. p[1], ["p" .. c.acute] = "p" .. p[1], ["r" .. c.caron] = "r" .. p[1], ["r" .. c.acute] = "r" .. p[2], ["s" .. c.caron] = "s" .. p[1], ["s" .. c.acute] = "s" .. p[2], ["w" .. c.acute] = "w" .. p[1], ["z" .. c.caron] = "z" .. p[1], ["z" .. c.acute] = "z" .. p[2], } } } -- Myanmar dotted form : https://www.unicode.org/Public/UNIDATA/StandardizedVariants.txt s["aio-displaytext"] = { from = {"([ကဂငတထပမယလဝဢေၵၸၺႀꩠꩡꩢꩣꩤꩥꩦꩫꩬꩯꩺ])"}, to = {"%1" .. c.VS01} } s["aio-stripdiacritics"] = { remove_diacritics = c.VS01, } s["esx-displaytext"] = { from = {"ǝ"}, to = {"ə"}, } s["esx-stripdiacritics"] = s["esx-displaytext"] s["phk-displaytext"] = s["aio-displaytext"] s["phk-stripdiacritics"] = s["aio-stripdiacritics"] s["kht-displaytext"] = s["aio-displaytext"] s["kht-stripdiacritics"] = s["aio-stripdiacritics"] export.shared = s --[==[ var: Short-term solution to override the standard substitution process, by forcing the module to substitute the entire text in one pass, if "cont" is given. This results in any PUA characters that are used as stand-ins for formatting being handled by the language-specific substitution process, which is usually undesirable. If the value is "none" then the formatting tags do not get turned into PUA characters in the first place. This override is provided for languages which use formatting between strings of text which might need to interact with each other (e.g. Korean 값이 transliterates as "gaps-i", but [[값]] has the formatting '''값'''[[-이]]. The normal process would split the text at the second '''.) ]==] export.substitution = { ["gmy"] = "none", ["ja"] = "cont", ["jje"] = "cont", ["ket"] = "none", ["ko"] = "cont", ["ko-ear"] = "cont", ["ru"] = "cont", ["th-new"] = "cont", ["sa"] = "cont", ["zkt"] = "cont", } --[==[ var: Code aliases. The left side is the alias and the right side is the canonical code. NOTE: These are gradually being deprecated, so should not be added to on a permanent basis. Temporary additions are permitted under reasonable circumstances (e.g. to facilitate changing a language's code). When an alias is no longer used, it should be removed. Aliases in this table are tracked at [[Wiktionary:Tracking/languages/LANG]]; see e.g. [[Special:WhatLinksHere/Wiktionary:Tracking/languages/VL.]] for the `VL.` alias. ]==] export.aliases = { ["EL."] = "la-ecc", ["LL."] = "la-lat", ["ML."] = "la-med", ["NL."] = "la-new", ["VL."] = "la-vul", ["grc-epc"] = "grc-epi", ["nds-DE"] = "nds-de", ["nds-NL"] = "nds-nl", ["roa-oan"] = "roa-ona", ["sa-cls"] = "cls", ["sa-ved"] = "vsn", } --[==[ var: Codes which are tracked. Note that all aliases listed above are also tracked, so should not be duplicated here. Tracking uses the same mechanism described above in the comment above `export.aliases`. ]==] export.track = { -- Codes duplicated between full and etymology-only languages. ["lzh-lit"] = true, ["lzh"] = true, -- Languages actively being converted to families. ["bh"] = true, -- inc-bih ["nan"] = true, -- zhx-nan -- Transcription scheme being changed. ["trk-pro"] = true, } return export mpdv2gw5dmhuaq7h15kvelv3q6pv46b Module:tungumál/gögn/2 828 119301 393950 2026-10-09T04:56:09Z Ow! That Hurts! 8952 skapað 393950 Scribunto text/plain local m_langdata = require("Module:tungumál/gögn") -- Loaded on demand, as it may not be needed (depending on the data). local function u(...) u = require("Module:string utilities").char return u(...) end local c = m_langdata.chars local p = m_langdata.puaChars local s = m_langdata.shared -- Ideally, we want to move these into [[Module:tungumál/gögn]], but because (a) it's necessary to use require on that module, and (b) they're only used in this data module, it's less memory-efficient to do that at the moment. If it becomes possible to use mw.loadData, then these should be moved there. s["de-Latn-sortkey"] = { remove_diacritics = c.grave .. c.acute .. c.circ .. c.diaer .. c.ringabove, from = {"æ", "œ", "ß"}, to = {"ae", "oe", "ss"} } s["de-Latn-standardchars"] = "AaÄäBbCcDdEeFfGgHhIiJjKkLlMmNnOoÖöPpQqRrSsẞßTtUuÜüVvWwXxYyZz" s["ka-stripdiacritics"] = {remove_diacritics = c.circ} s["no-sortkey"] = { remove_diacritics = c.grave .. c.acute .. c.circ .. c.tilde .. c.macron .. c.dacute .. c.caron .. c.cedilla, remove_exceptions = {"å"}, from = {"æ", "ø", "å"}, to = {"z" .. p[1], "z" .. p[2], "z" .. p[3]} } s["no-standardchars"] = "AaBbDdEeFfGgHhIiJjKkLlMmNnOoPpRrSsTtUuVvYyÆæØøÅå" .. c.punc s["sa-Deva-stripdiacritics"] = { -- Don't use remove_diacritics for accent marks, as १ and ३ should also be removed if (and only if) they carry any. from = {"ॐ", "[१३]?[" .. c.anudatta .. c.udatta .. c.dsvarita .. c.tsvarita .. "]+"}, to = {"ओँ"}, } s["tg-stripdiacritics"] = {remove_diacritics = c.grave .. c.acute} s["tk-stripdiacritics"] = {remove_diacritics = c.macron} local m = {} m["aa"] = { "Afar", 27811, "cus-eas", "Latn, Ethi", strip_diacritics = { Latn = {remove_diacritics = c.acute}, }, } m["ab"] = { "Abkhaz", 5111, "cau-abz", "Cyrl, Geor, Latn", translit = { Cyrl = "ab-translit", -- Geor translit in [[Module:scripts/data]] }, override_translit = true, display_text = { Cyrl = s["cau-Cyrl-displaytext"] }, strip_diacritics = { Cyrl = { remove_diacritics = c.acute, from = {"^а%-"}, to = {"а"}, }, Latn = s["cau-Latn-stripdiacritics"], }, sort_key = { Cyrl = { from = { "х'ә", -- 3 chars "гь", "гә", "ӷь", "ҕь", "ӷә", "ҕә", "дә", "ё", "жь", "жә", "ҙә", "ӡә", "ӡ'", "кь", "кә", "қь", "қә", "ҟь", "ҟә", "ҫә", "тә", "ҭә", "ф'", "хь", "хә", "х'", "ҳә", "ць", "цә", "ц'", "ҵә", "ҵ'", "шь", "шә", "џь", -- 2 chars "ӷ", "ҕ", "ҙ", "ӡ", "қ", "ҟ", "ԥ", "ҧ", "ҫ", "ҭ", "ҳ", "ҵ", "ҷ", "ҽ", "ҿ", "ҩ", "џ", "ә", -- 1 char "^а", }, to = { "х" .. p[4], "г" .. p[1], "г" .. p[2], "г" .. p[5], "г" .. p[6], "г" .. p[7], "г" .. p[8], "д" .. p[1], "е" .. p[1], "ж" .. p[1], "ж" .. p[2], "з" .. p[2], "з" .. p[4], "з" .. p[5], "к" .. p[1], "к" .. p[2], "к" .. p[4], "к" .. p[5], "к" .. p[7], "к" .. p[8], "с" .. p[2], "т" .. p[1], "т" .. p[3], "ф" .. p[1], "х" .. p[1], "х" .. p[2], "х" .. p[3], "х" .. p[6], "ц" .. p[1], "ц" .. p[2], "ц" .. p[3], "ц" .. p[5], "ц" .. p[6], "ш" .. p[1], "ш" .. p[2], "ы" .. p[3], "г" .. p[3], "г" .. p[4], "з" .. p[1], "з" .. p[3], "к" .. p[3], "к" .. p[6], "п" .. p[1], "п" .. p[2], "с" .. p[1], "т" .. p[2], "х" .. p[5], "ц" .. p[4], "ч" .. p[1], "ч" .. p[2], "ч" .. p[3], "ы" .. p[1], "ы" .. p[2], "ь" .. p[1], "", } }, }, } m["ae"] = { "Avestan", 29572, "ira-cen", "Avst, Gujr, Deva", translit = { Avst = "Avst-translit" }, } m["af"] = { "Afrikaans", 14196, "gmw-frk", "Latn, Arab", ancestors = "nl", sort_key = { Latn = { remove_diacritics = c.grave .. c.acute .. c.circ .. c.tilde .. c.diaer .. c.ringabove .. c.cedilla .. "'", from = {"['ʼ]n"}, to = {"n" .. p[1]} } }, } m["ak"] = { "Akan", 28026, "alv-ctn", "Latn", } m["am"] = { "Amharic", 28244, "sem-eth", "Ethi", translit = "Ethi-translit", } m["an"] = { "Aragonese", 8765, "roa-nar", "Latn", } m["ar"] = { "Arabic", 13955, "sem-arb", "Arab, Hebr, Syrc, Brai, Nbat", translit = { Arab = "ar-translit" }, strip_diacritics = { Arab = "ar-stripdiacritics", }, -- Hebr display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["as"] = { "Assamese", 29401, "inc-bas", "as-Beng", ancestors = "inc-mas", translit = "as-translit", } m["av"] = { "Avar", 29561, "cau-ava", "Cyrl, Latn, Arab", ancestors = "oav", translit = { Cyrl = "cau-nec-translit", Arab = "ar-translit", }, override_translit = true, display_text = { Cyrl = s["cau-Cyrl-displaytext"], }, strip_diacritics = { Cyrl = s["cau-Cyrl-stripdiacritics"], Latn = s["cau-Latn-stripdiacritics"], }, sort_key = { Cyrl = { from = {"гъ", "гь", "гӏ", "ё", "кк", "къ", "кь", "кӏ", "лъ", "лӏ", "тӏ", "хх", "хъ", "хь", "хӏ", "цӏ", "чӏ"}, to = {"г" .. p[1], "г" .. p[2], "г" .. p[3], "е" .. p[1], "к" .. p[1], "к" .. p[2], "к" .. p[3], "к" .. p[4], "л" .. p[1], "л" .. p[2], "т" .. p[1], "х" .. p[1], "х" .. p[2], "х" .. p[3], "х" .. p[4], "ц" .. p[1], "ч" .. p[1]} }, }, } m["ay"] = { "Aymara", 4627, "sai-aym", "Latn", } m["az"] = { "Azerbaijani", 9292, "trk-ogz", "Latn, Cyrl, Arab", ancestors = "trk-oat", dotted_dotless_i = true, strip_diacritics = { Latn = { from = {"ʼ"}, to = {"'"}, }, Arab = { module = "ar-stripdiacritics", ["from"] = { "ۆ", "ۇ", "وْ", "ڲ", "ؽ", }, ["to"] = { "و", "و", "و", "گ", "ی", }, }, }, display_text = { Latn = { from = {"'"}, to = {"ʼ"} } }, sort_key = { Latn = { from = { "i", -- Ensure "i" comes after "ı". "ç", "ə", "ğ", "x", "ı", "q", "ö", "ş", "ü", "w" }, to = { "i" .. p[1], "c" .. p[1], "e" .. p[1], "g" .. p[1], "h" .. p[1], "i", "k" .. p[1], "o" .. p[1], "s" .. p[1], "u" .. p[1], "z" .. p[1] } }, Cyrl = { from = {"ғ", "ә", "ы", "ј", "ҝ", "ө", "ү", "һ", "ҹ"}, to = {"г" .. p[1], "е" .. p[1], "и" .. p[1], "и" .. p[2], "к" .. p[1], "о" .. p[1], "у" .. p[1], "х" .. p[1], "ч" .. p[1]} }, }, } m["ba"] = { "Bashkir", 13389, "trk-kbu", "Cyrl", translit = "ba-translit", override_translit = true, sort_key = { from = {"ғ", "ҙ", "ё", "ҡ", "ң", "ө", "ҫ", "ү", "һ", "ә"}, to = {"г" .. p[1], "д" .. p[1], "е" .. p[1], "к" .. p[1], "н" .. p[1], "о" .. p[1], "с" .. p[1], "у" .. p[1], "х" .. p[1], "э" .. p[1]} }, } m["be"] = { "Belarusian", 9091, "zle", "Cyrl, Latn", ancestors = "zle-mbe", translit = { Cyrl = "be-translit", }, strip_diacritics = { Cyrl = { remove_diacritics = c.grave .. c.acute, }, Latn = { remove_diacritics = c.grave .. c.acute, remove_exceptions = {"Ć", "ć", "Ń", "ń", "Ś", "ś", "Ź", "ź"}, }, }, sort_key = { Cyrl = { remove_diacritics = c.grave .. c.acute, from = {"ґ", "ё", "і", "ў"}, to = {"г" .. p[1], "е" .. p[1], "и" .. p[1], "у" .. p[1]} }, Latn = { remove_diacritics = c.grave .. c.acute, remove_exceptions = {"ć", "ń", "ś", "ź"}, from = {"ć", "č", "dz", "dź", "dž", "ch", "ł", "ń", "ś", "š", "ŭ", "ź", "ž"}, to = {"c" .. p[1], "c" .. p[2], "d" .. p[1], "d" .. p[2], "d" .. p[3], "h" .. p[1], "l" .. p[1], "n" .. p[1], "s" .. p[1], "s" .. p[2], "u" .. p[1], "z" .. p[1], "z" .. p[2]} }, }, standard_chars = { Cyrl = "АаБбВвГгДдЕеЁёЖжЗзІіЙйКкЛлМмНнОоПпРрСсТтУуЎўФфХхЦцЧчШшЫыЬьЭэЮюЯя", Latn = "AaBbCcĆćČčDdEeFfGgHhIiJjKkLlŁłMmNnŃńOoPpRrSsŚśŠšTtUuŬŭVvYyZzŹźŽž", (c.punc:gsub("'", "")) -- Exclude apostrophe. }, } m["bg"] = { "Bulgarian", 7918, "zls", "Cyrl", ancestors = "cu-bgm", translit = "bg-translit", strip_diacritics = { remove_diacritics = c.grave .. c.acute, remove_exceptions = {"%f[^%z%s]ѝ%f[%z%s]"}, }, sort_key = { remove_diacritics = c.grave .. c.acute, remove_exceptions = {"%f[^%z%s]ѝ%f[%z%s]"}, }, standard_chars = "АаБбВвГгДдЕеЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЬьЮюЯя" .. c.punc, } m["bh"] = { "Bihari", 135305, "inc-eas", "Deva", } m["bi"] = { "Bislama", 35452, "crp", "Latn", ancestors = "en", } m["bm"] = { "Bambara", 33243, "dmn-emn", "Latn, Nkoo", sort_key = { Latn = { from = {"ɛ", "ɲ", "ŋ", "ɔ"}, to = {"e" .. p[1], "n" .. p[1], "n" .. p[2], "o" .. p[1]} }, }, } m["bn"] = { "Bengali", 9610, "inc-bas", "Beng, Newa", ancestors = "inc-mbn", translit = { Beng = "bn-translit" }, } m["bo"] = { "Tibetan", 34271, "sit-tib", "Tibt", -- sometimes Deva? ancestors = "xct", override_translit = true, -- Tibt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["br"] = { "Breton", 12107, "cel-brs", "Latn", ancestors = "xbm", sort_key = { from = {"ch", "c['ʼ’]h"}, to = {"c" .. p[1], "c" .. p[2]} }, } m["ca"] = { "Catalan", 7026, "roa-ocr", "Latn", ancestors = "roa-oca", sort_key = {remove_diacritics = c.grave .. c.acute .. c.diaer .. c.cedilla .. "·"}, standard_chars = "AaÀàBbCcÇçDdEeÉéÈèFfGgHhIiÍíÏïJjLlMmNnOoÓóÒòPpQqRrSsTtUuÚúÜüVvXxYyZz·" .. c.punc, } m["ce"] = { "Chechen", 33350, "cau-vay", "Cyrl, Latn, Arab", translit = { Cyrl = "cau-nec-translit", Arab = "ar-translit", }, override_translit = true, display_text = { Cyrl = s["cau-Cyrl-displaytext"] }, strip_diacritics = { Cyrl = s["cau-Cyrl-stripdiacritics"], Latn = s["cau-Latn-stripdiacritics"], }, sort_key = { Cyrl = { from = {"аь", "гӏ", "ё", "кх", "къ", "кӏ", "оь", "пӏ", "тӏ", "уь", "хь", "хӏ", "цӏ", "чӏ", "юь", "яь"}, to = {"а" .. p[1], "г" .. p[1], "е" .. p[1], "к" .. p[1], "к" .. p[2], "к" .. p[3], "о" .. p[1], "п" .. p[1], "т" .. p[1], "у" .. p[1], "х" .. p[1], "х" .. p[2], "ц" .. p[1], "ч" .. p[1], "ю" .. p[1], "я" .. p[1]} }, }, } m["ch"] = { "Chamorro", 33262, "poz", "Latn", sort_key = { remove_diacritics = "'", from = {"å", "ch", "ñ", "ng"}, to = {"a" .. p[1], "c" .. p[1], "n" .. p[1], "n" .. p[2]} }, } m["co"] = { "Corsican", 33111, "roa-itr", "Latn", sort_key = { from = {"chj", "ghj", "sc", "sg"}, to = {"c" .. p[1], "g" .. p[1], "s" .. p[1], "s" .. p[2]} }, standard_chars = "AaÀàBbCcDdEeÈèFfGgHhIiÌìÏïJjLlMmNnOoÒòPpQqRrSsTtUuÙùÜüVvZz" .. c.punc, } m["cr"] = { "Cree", 33390, "alg", "Latn, Cans", translit = { Cans = "cr-translit" }, } m["cs"] = { "Czech", 9056, "zlw", "Latn", ancestors = "cs-ear", sort_key = { from = {"á", "č", "ď", "é", "ě", "ch", "í", "ň", "ó", "ř", "š", "ť", "ú", "ů", "ý", "ž"}, to = {"a" .. p[1], "c" .. p[1], "d" .. p[1], "e" .. p[1], "e" .. p[2], "h" .. p[1], "i" .. p[1], "n" .. p[1], "o" .. p[1], "r" .. p[1], "s" .. p[1], "t" .. p[1], "u" .. p[1], "u" .. p[2], "y" .. p[1], "z" .. p[1]} }, standard_chars = "AaÁáBbCcČčDdĎďEeÉéĚěFfGgHhIiÍíJjKkLlMmNnŇňOoÓóPpRrŘřSsŠšTtŤťUuÚúŮůVvYyÝýZzŽž" .. c.punc, } m["cu"] = { "Old Church Slavonic", 35499, "zls", "Cyrs, Glag, Zname", translit = { Cyrs = "Cyrs-translit", Glag = "Glag-translit" }, -- Cyrs strip_diacritics, sort_key in [[Module:scripts/data]] } m["cv"] = { "Chuvash", 33348, "trk-ogr", "Cyrl", ancestors = "cv-mid", translit = "cv-translit", override_translit = true, sort_key = { from = {"ӑ", "ё", "ӗ", "ҫ", "ӳ"}, to = {"а" .. p[1], "е" .. p[1], "е" .. p[2], "с" .. p[1], "у" .. p[1]} }, } m["cy"] = { "Welsh", 9309, "cel-brw", "Latn", ancestors = "wlm", sort_key = { remove_diacritics = c.grave .. c.acute .. c.circ .. c.diaer .. "'", from = {"ch", "dd", "ff", "ng", "ll", "ph", "rh", "th"}, to = {"c" .. p[1], "d" .. p[1], "f" .. p[1], "g" .. p[1], "l" .. p[1], "p" .. p[1], "r" .. p[1], "t" .. p[1]} }, standard_chars = "ÂâAaBbCcDdEeÊêFfGgHhIiÎîLlMmNnOoÔôPpRrSsTtUuÛûWwŴŵYyŶŷ" .. c.punc, } m["da"] = { "Danish", 9035, "gmq-eas", "Latn", ancestors = "gmq-oda", sort_key = { remove_diacritics = c.grave .. c.acute .. c.circ .. c.tilde .. c.macron .. c.dacute .. c.caron .. c.cedilla, remove_exceptions = {"å"}, from = {"æ", "ø", "å"}, to = {"z" .. p[1], "z" .. p[2], "z" .. p[3]} }, standard_chars = "AaBbDdEeFfGgHhIiJjKkLlMmNnOoPpRrSsTtUuVvYyÆæØøÅå" .. c.punc, } m["de"] = { "German", 188, "gmw-hgm", "Latn, Latf, Brai", ancestors = "de-ear", sort_key = { Latn = s["de-Latn-sortkey"], Latf = s["de-Latn-sortkey"], }, standard_chars = { Latn = s["de-Latn-standardchars"], Latf = s["de-Latn-standardchars"], Brai = c.braille, c.punc } } m["dv"] = { "Dhivehi", 32656, "inc-ins", "Thaa, Diak", translit = { Thaa = "dv-translit", Diak = "Diak-translit", }, ancestors = "dv-old", override_translit = true, } m["dz"] = { "Dzongkha", 33081, "sit-tib", "Tibt", ancestors = "xct", override_translit = true, -- Tibt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["ee"] = { "Ewe", 30005, "alv-gbe", "Latn", sort_key = { remove_diacritics = c.tilde, from = {"ɖ", "dz", "ɛ", "ƒ", "gb", "ɣ", "kp", "ny", "ŋ", "ɔ", "ts", "ʋ"}, to = {"d" .. p[1], "d" .. p[2], "e" .. p[1], "f" .. p[1], "g" .. p[1], "g" .. p[2], "k" .. p[1], "n" .. p[1], "n" .. p[2], "o" .. p[1], "t" .. p[1], "v" .. p[1]} }, } m["el"] = { "Greek", 9129, "grk", "Grek, Polyt, Brai", ancestors = "el-kth", translit = "el-translit", override_translit = true, -- Grek and Polyt display_text, strip_diacritics, sort_key in [[Module:scripts/data]] standard_chars = { Grek = "΅·ͺ΄ΑαΆάΒβΓγΔδΕεέΈΖζΗηΉήΘθΙιΊίΪϊΐΚκΛλΜμΝνΞξΟοΌόΠπΡρΣσςΤτΥυΎύΫϋΰΦφΧχΨψΩωΏώ", Brai = c.braille, c.punc }, } m["en"] = { "English", 1860, "gmw-ang", "Latn, Brai, Shaw, Dsrt", -- entries in Shaw or Dsrt might require prior discussion wikimedia_codes = "en, simple", ancestors = "en-ear", sort_key = { Latn = { -- Many of these are needed for sorting language names. remove_diacritics = "'\"%-%.,%s·ʻʼ" .. c.diacritics, -- These are found in pagenames. from = {"[ɒæ🅱¢©ᴄðđəǝɜɡħʜıɨłŋɲøɔœꝑꝓꝕßʋ]"}, to = {{ ["ɒ"] = "a", ["æ"] = "ae", ["🅱"] = "b", ["¢"] = "c", ["©"] = "c", ["ᴄ"] = "c", ["ð"] = "d", ["đ"] = "d", ["ə"] = "e", ["ǝ"] = "e", ["ɜ"] = "e", ["ɡ"] = "g", ["ħ"] = "h", ["ʜ"] = "h", ["ı"] = "i", ["ɨ"] = "i", ["ł"] = "l", ["ŋ"] = "n", ["ɲ"] = "n", ["ø"] = "o", ["ɔ"] = "o", ["œ"] = "oe", ["ꝑ"] = "p", ["ꝓ"] = "p", ["ꝕ"] = "p", ["ß"] = "ss", ["ʋ"] = "v", }}, }, }, standard_chars = { Latn = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz", Brai = c.braille, c.punc }, } m["eo"] = { "Esperanto", 143, "art", "Latn", sort_key = { remove_diacritics = c.grave .. c.acute, from = {"ĉ", "ĝ", "ĥ", "ĵ", "ŝ", "ŭ"}, to = {"c" .. p[1], "g" .. p[1], "h" .. p[1], "j" .. p[1], "s" .. p[1], "u" .. p[1]} }, standard_chars = "AaBbCcĈĉDdEeFfGgĜĝHhĤĥIiJjĴĵKkLlMmNnOoPpRrSsŜŝTtUuŬŭVvZz" .. c.punc, } m["es"] = { "Spanish", 1321, "roa-cas", "Latn, Brai", ancestors = "es-ear", sort_key = { Latn = { remove_exceptions = {"ñ"}, remove_diacritics = c.grave .. c.acute .. c.circ .. c.tilde .. c.macron .. c.diaer .. c.cedilla, from = {"ª", "æ", "ñ", "º", "œ"}, to = {"a", "ae", "n" .. p[1], "o", "oe"} }, }, standard_chars = { Latn = "AaÁáBbCcDdEeÉéFfGgHhIiÍíJjLlMmNnÑñOoÓóPpQqRrSsTtUuÚúÜüVvXxYyZz", Brai = c.braille, c.punc }, } m["et"] = { "Estonian", 9072, "urj-fin", "Latn", sort_key = { from = { "š", "ž", "õ", "ä", "ö", "ü", -- 2 chars "z" -- 1 char }, to = { "s" .. p[1], "s" .. p[3], "w" .. p[1], "w" .. p[2], "w" .. p[3], "w" .. p[4], "s" .. p[2] } }, standard_chars = "AaBbDdEeFfGgHhIiJjKkLlMmNnOoPpRrSsTtUuVvÕõÄäÖöÜü" .. c.punc, } m["eu"] = { "Basque", 8752, "euq", "Latn", sort_key = { from = {"ç", "ñ"}, to = {"c" .. p[1], "n" .. p[1]} }, standard_chars = "AaBbDdEeFfGgHhIiJjKkLlMmNnÑñOoPpRrSsTtUuXxZz" .. c.punc, } m["fa"] = { "Persian", 9168, "ira-swi", "Arab, Hebr", ancestors = "fa-cls", strip_diacritics = { Arab = { -- character "ۂ" code U+06C2 to "ه" and "هٔ" (U+0647 + U+0654) to "ه"; hamzatu l-waṣli to a regular alif from = {"هٔ", "ٱ"}, -- character "ۂ" code U+06C2 to "ه"; hamzatu l-waṣli to a regular alif to = {"ه", "ا"}, remove_diacritics = c.fathatan .. c.dammatan .. c.kasratan .. c.fatha .. c.damma .. c.kasra .. c.shadda .. c.sukun .. c.superalef, }, }, -- Hebr display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["ff"] = { "Fula", 33454, "alv-fwo", "Latn, Adlm", } m["fi"] = { "Finnish", 1412, "urj-fin", "Latn", display_text = { from = {"'"}, to = {"’"} }, strip_diacritics = { -- used to indicate gemination of the next consonant remove_diacritics = "ˣ", from = {"’"}, to = {"'"}, }, sort_key = { -- [[Appendix:Finnish alphabet#Collation]] + "aͤ" and "oͤ" as historical variants of "ä" and "ö". remove_diacritics = "'’:" .. c.diacritics, remove_exceptions = { "a[" .. c.ringabove .. c.diaer .. c.small_e .. "]", -- åäaͤ "o[" .. c.diaer .. c.tilde .. c.dacute .. c.small_e .. "]", -- öõőoͤ "u[" .. c.diaer .. c.dacute .. "]" -- üű }, from = {"æ", "[ðđ]", "ł", "ŋ", "œ", "ß", "þ", "u[" .. c.diaer .. c.dacute .. "]", "å", "aͤ", "o[" .. c.tilde .. c.dacute .. c.small_e .. "]", "ø", "(.)['%-]"}, to = {"ae", "d", "l", "n", "oe", "ss", "th", "y", "z" .. p[1], "ä", "ö", "ö", "%1"} }, standard_chars = "AaBbDdEeFfGgHhIiJjKkLlMmNnOoPpRrSsTtUuVvYyÄäÖö" .. c.punc, } m["fj"] = { "Fijian", 33295, "poz-pcc", "Latn", } m["fo"] = { "Faroese", 25258, "gmq-ins", "Latn", sort_key = { from = {"á", "ð", "í", "ó", "ú", "ý", "æ", "ø"}, to = {"a" .. p[1], "d" .. p[1], "i" .. p[1], "o" .. p[1], "u" .. p[1], "y" .. p[1], "z" .. p[1], "z" .. p[2]} }, standard_chars = "AaÁáBbDdÐðEeFfGgHhIiÍíJjKkLlMmNnOoÓóPpRrSsTtUuÚúVvYyÝýÆæØø" .. c.punc, } m["fr"] = { "French", 150, "roa-oil", "Latn, Brai", ancestors = "frm", sort_key = { Latn = s["roa-oil-sortkey"] }, standard_chars = { Latn = "AaÀàÂâBbCcÇçDdEeÉéÈèÊêËëFfGgHhIiÎîÏïJjLlMmNnOoÔôŒœPpQqRrSsTtUuÙùÛûÜüVvXxYyZz", Brai = c.braille, c.punc }, } m["fy"] = { "West Frisian", 27175, "gmw-fri", "Latn", sort_key = { remove_diacritics = c.grave .. c.acute .. c.circ .. c.diaer, from = {"y"}, to = {"i"} }, standard_chars = "AaâäàÆæBbCcDdEeéêëèFfGgHhIiïìYyỳJjKkLlMmNnOoôöòPpRrSsTtUuúûüùVvWwZz" .. c.punc, } m["ga"] = { "Irish", 9142, "cel-gae", "Latn, Latg", ancestors = "mga", sort_key = { remove_diacritics = c.acute, from = {"ḃ", "ċ", "ḋ", "ḟ", "ġ", "ṁ", "ṗ", "ṡ", "ṫ"}, to = {"bh", "ch", "dh", "fh", "gh", "mh", "ph", "sh", "th"} }, standard_chars = "AaÁáBbCcDdEeÉéFfGgHhIiÍíLlMmNnOoÓóPpRrSsTtUuÚúVv" .. c.punc, } m["gd"] = { "Scottish Gaelic", 9314, "cel-gae", "Latn, Latg", ancestors = "mga", sort_key = {remove_diacritics = c.grave .. c.acute}, standard_chars = "AaÀàBbCcDdEeÈèFfGgHhIiÌìLlMmNnOoÒòPpRrSsTtUuÙù" .. c.punc, } m["gl"] = { "Galician", 9307, "roa-gap", "Latn", sort_key = { remove_diacritics = c.acute, from = {"ñ"}, to = {"n" .. p[1]} }, standard_chars = "AaÁáBbCcDdEeÉéFfGgHhIiÍíÏïLlMmNnÑñOoÓóPpQqRrSsTtUuÚúÜüVvXxZz" .. c.punc, } m["gu"] = { "Gujarati", 5137, "inc-wes", "Arab, Gujr", ancestors = "inc-mgu", translit = { Gujr = "gu-translit", }, strip_diacritics = { Arab = {remove_diacritics = c.fathatan .. c.dammatan .. c.kasratan .. c.fatha .. c.damma .. c.kasra .. c.kasra .. c.shadda .. c.sukun}, Gujr = {remove_diacritics = "઼"}, }, } m["gv"] = { "Manx", 12175, "cel-gae", "Latn", ancestors = "mga", sort_key = {remove_diacritics = c.cedilla .. "-"}, standard_chars = "AaBbCcÇçDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwYy" .. c.punc, } m["ha"] = { "Hausa", 56475, "cdc-wst", "Latn, Arab", strip_diacritics = { Latn = {remove_diacritics = c.grave .. c.acute .. c.circ .. c.tilde .. c.macron} }, sort_key = { Latn = { from = {"ɓ", "b'", "ɗ", "d'", "ƙ", "k'", "sh", "ƴ", "'y"}, to = {"b" .. p[1], "b" .. p[2], "d" .. p[1], "d" .. p[2], "k" .. p[1], "k" .. p[2], "s" .. p[1], "y" .. p[1], "y" .. p[2]} }, }, } m["he"] = { "Hebrew", 9288, "sem-can", "Hebr, Phnx, Brai, Samr", ancestors = "he-med", -- Hebr display_text, strip_diacritics, sort_key in [[Module:scripts/data]] -- Samr strip_diacritics, sort_key in [[Module:scripts/data]] -- Phnx translit in [[Module:scripts/data]] (NOTE: not present before, presumably an accidental omission) } m["hi"] = { "Hindi", 1568, "inc-hnd", "Deva, Kthi, Newa", translit = { Deva = "hi-translit" }, standard_chars = { Deva = "अआइईउऊएऐओऔकखगघङचछजझञटठडढणतथदधनपफबभमयरलवशषसहत्रज्ञक्षक़ख़ग़ज़झ़ड़ढ़फ़काखागाघाङाचाछाजाझाञाटाठाडाढाणाताथादाधानापाफाबाभामायारालावाशाषासाहात्राज्ञाक्षाक़ाख़ाग़ाज़ाझ़ाड़ाढ़ाफ़ाकिखिगिघिङिचिछिजिझिञिटिठिडिढिणितिथिदिधिनिपिफिबिभिमियिरिलिविशिषिसिहित्रिज्ञिक्षिक़िख़िग़िज़िझ़िड़िढ़िफ़िकीखीगीघीङीचीछीजीझीञीटीठीडीढीणीतीथीदीधीनीपीफीबीभीमीयीरीलीवीशीषीसीहीत्रीज्ञीक्षीक़ीख़ीग़ीज़ीझ़ीड़ीढ़ीफ़ीकुखुगुघुङुचुछुजुझुञुटुठुडुढुणुतुथुदुधुनुपुफुबुभुमुयुरुलुवुशुषुसुहुत्रुज्ञुक्षुक़ुख़ुग़ुज़ुझ़ुड़ुढ़ुफ़ुकूखूगूघूङूचूछूजूझूञूटूठूडूढूणूतूथूदूधूनूपूफूबूभूमूयूरूलूवूशूषूसूहूत्रूज्ञूक्षूक़ूख़ूग़ूज़ूझ़ूड़ूढ़ूफ़ूकेखेगेघेङेचेछेजेझेञेटेठेडेढेणेतेथेदेधेनेपेफेबेभेमेयेरेलेवेशेषेसेहेत्रेज्ञेक्षेक़ेख़ेग़ेज़ेझ़ेड़ेढ़ेफ़ेकैखैगैघैङैचैछैजैझैञैटैठैडैढैणैतैथैदैधैनैपैफैबैभैमैयैरैलैवैशैषैसैहैत्रैज्ञैक्षैक़ैख़ैग़ैज़ैझ़ैड़ैढ़ैफ़ैकोखोगोघोङोचोछोजोझोञोटोठोडोढोणोतोथोदोधोनोपोफोबोभोमोयोरोलोवोशोषोसोहोत्रोज्ञोक्षोक़ोख़ोग़ोज़ोझ़ोड़ोढ़ोफ़ोकौखौगौघौङौचौछौजौझौञौटौठौडौढौणौतौथौदौधौनौपौफौबौभौमौयौरौलौवौशौषौसौहौत्रौज्ञौक्षौक़ौख़ौग़ौज़ौझ़ौड़ौढ़ौफ़ौक्ख्ग्घ्ङ्च्छ्ज्झ्ञ्ट्ठ्ड्ढ्ण्त्थ्द्ध्न्प्फ्ब्भ्म्य्र्ल्व्श्ष्स्ह्त्र्ज्ञ्क्ष्क़्ख़्ग़्ज़्झ़्ड़्ढ़्फ़्।॥०१२३४५६७८९॰", c.punc }, } m["ho"] = { "Hiri Motu", 33617, "crp", "Latn", ancestors = "meu", } m["ht"] = { "Haitian Creole", 33491, "crp", "Latn", ancestors = "ht-sdm", sort_key = { from = { "oun", -- 3 chars "an", "ch", "è", "en", "ng", "ò", "on", "ou", "ui" -- 2 chars }, to = { "o" .. p[4], "a" .. p[1], "c" .. p[1], "e" .. p[1], "e" .. p[2], "n" .. p[1], "o" .. p[1], "o" .. p[2], "o" .. p[3], "u" .. p[1] } }, } m["hu"] = { "Hungarian", 9067, "urj-ugr", "Latn, Hung", ancestors = "ohu", sort_key = { Latn = { from = { "dzs", -- 3 chars "á", "cs", "dz", "é", "gy", "í", "ly", "ny", "ó", "ö", "ő", "sz", "ty", "ú", "ü", "ű", "zs", -- 2 chars }, to = { "d" .. p[2], "a" .. p[1], "c" .. p[1], "d" .. p[1], "e" .. p[1], "g" .. p[1], "i" .. p[1], "l" .. p[1], "n" .. p[1], "o" .. p[1], "o" .. p[2], "o" .. p[3], "s" .. p[1], "t" .. p[1], "u" .. p[1], "u" .. p[2], "u" .. p[3], "z" .. p[1], } }, }, standard_chars = { Latn = "AaÁáBbCcDdEeÉéFfGgHhIiÍíJjKkLlMmNnOoÓóÖöŐőPpQqRrSsTtUuÚúÜüŰűVvWwXxYyZz", c.punc }, } m["hy"] = { "Armenian", 8785, "hyx", "Armn, Brai", ancestors = "axm", -- Armn translit in [[Module:scripts/data]] override_translit = true, strip_diacritics = { Armn = { remove_diacritics = "՛՜՞՟", from = {"եւ", "<sup>յ</sup>", "<sup>ի</sup>", "<sup>է</sup>", "յ̵", "ՙ", "՚"}, to = {"և", "յ", "ի", "է", "ֈ", "ʻ", "’"} }, }, sort_key = { Armn = { from = { "ու", "եւ", -- 2 chars "և" -- 1 char }, to = { "ւ", "եվ", "եվ" } }, }, } m["hz"] = { "Herero", 33315, "bnt-swb", "Latn", } m["ia"] = { "Interlingua", 35934, "art", "Latn", } m["id"] = { "Indonesian", 9240, "poz-mly", "Latn", ancestors = "ms", standard_chars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz" .. c.punc, } m["ie"] = { "Interlingue", 35850, "art", "Latn", type = "appendix-constructed", strip_diacritics = {remove_diacritics = c.grave .. c.acute .. c.circ}, } m["ig"] = { "Igbo", 33578, "alv-igb", "Latn", strip_diacritics = {remove_diacritics = c.grave .. c.acute .. c.macron}, sort_key = { from = {"gb", "gh", "gw", "ị", "kp", "kw", "ṅ", "nw", "ny", "ọ", "sh", "ụ"}, to = {"g" .. p[1], "g" .. p[2], "g" .. p[3], "i" .. p[1], "k" .. p[1], "k" .. p[2], "n" .. p[1], "n" .. p[2], "n" .. p[3], "o" .. p[1], "s" .. p[1], "u" .. p[1]} }, } m["ii"] = { "Nuosu", 34235, "tbq-nlo", "Yiii", translit = "ii-translit", } m["ik"] = { "Inupiaq", 27183, "esx-inu", "Latn", sort_key = { from = { "ch", "ġ", "dj", "ḷ", "ł̣", "ñ", "ng", "r̂", "sr", "zr", -- 2 chars "ł", "ŋ", "ʼ" -- 1 char }, to = { "c" .. p[1], "g" .. p[1], "h" .. p[1], "l" .. p[1], "l" .. p[3], "n" .. p[1], "n" .. p[2], "r" .. p[1], "s" .. p[1], "z" .. p[1], "l" .. p[2], "n" .. p[2], "z" .. p[2] } }, } m["io"] = { "Ido", 35224, "art", "Latn", } m["is"] = { "Icelandic", 294, "gmq-ins", "Latn", sort_key = { from = {"á", "ð", "é", "í", "ó", "ú", "ý", "þ", "æ", "ö"}, to = {"a" .. p[1], "d" .. p[1], "e" .. p[1], "i" .. p[1], "o" .. p[1], "u" .. p[1], "y" .. p[1], "z" .. p[1], "z" .. p[2], "z" .. p[3]} }, standard_chars = "AaÁáBbDdÐðEeÉéFfGgHhIiÍíJjKkLlMmNnOoÓóPpRrSsTtUuÚúVvXxYyÝýÞþÆæÖö" .. c.punc, } m["it"] = { "Italian", 652, "roa-itr", "Latn", ancestors = "roa-oit", sort_key = {remove_diacritics = c.grave .. c.acute .. c.circ .. c.diaer .. c.ringabove}, standard_chars = "AaÀàBbCcDdEeÈèÉéFfGgHhIiÌìLlMmNnOoÒòPpQqRrSsTtUuÙùVvZz" .. c.punc, } m["iu"] = { "Inuktitut", 29921, "esx-inu", "Cans, Latn", translit = { Cans = "cr-translit" }, override_translit = true, } m["ja"] = { "Japanese", 5287, "jpx", "Jpan, Latn, Brai", ancestors = "ja-ear", translit = s["jpx-translit"], link_tr = true, display_text = s["jpx-displaytext"], strip_diacritics = s["jpx-stripdiacritics"], sort_key = s["jpx-sortkey"], } m["jv"] = { "Javanese", 33549, "poz", "Latn, Java, Arab", ancestors = "kaw", translit = { Java = "jv-translit" }, link_tr = true, strip_diacritics = { Latn = {remove_diacritics = c.circ} -- Modern jv don't use ê }, sort_key = { Latn = { from = {"å", "dh", "é", "è", "ng", "ny", "th"}, to = {"a" .. p[1], "d" .. p[1], "e" .. p[1], "e" .. p[2], "n" .. p[1], "n" .. p[2], "t" .. p[1]} }, }, } m["ka"] = { "Georgian", 8108, "ccs-gzn", "Geor, Geok, Hebr", -- Hebr is used to write Judeo-Georgian ancestors = "ka-mid", -- Geor, Geok translit in [[Module:scripts/data]] override_translit = true, strip_diacritics = { Geor = s["ka-stripdiacritics"], Geok = s["ka-stripdiacritics"], }, -- Hebr display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["kg"] = { "Kongo", 33702, "bnt-kng", "Latn", } m["ki"] = { "Kikuyu", 33587, "bnt-kka", "Latn", } m["kj"] = { "Kwanyama", 1405077, "bnt-ova", "Latn", } m["kk"] = { "Kazakh", 9252, "trk-kno", "Cyrl, Latn, Arab", translit = "kk-translit", -- override_translit = true, sort_key = { Cyrl = { from = {"ә", "ғ", "ё", "қ", "ң", "ө", "ұ", "ү", "һ", "і"}, to = {"а" .. p[1], "г" .. p[1], "е" .. p[1], "к" .. p[1], "н" .. p[1], "о" .. p[1], "у" .. p[1], "у" .. p[2], "х" .. p[1], "ы" .. p[1]} }, }, standard_chars = { Cyrl = "АаӘәБбВвГгҒғДдЕеЁёЖжЗзИиЙйКкҚқЛлМмНнҢңОоӨөПпРрСсТтУуҰұҮүФфХхҺһЦцЧчШшЩщЪъЫыІіЬьЭэЮюЯя", c.punc }, } m["kl"] = { "Greenlandic", 25355, "esx-inu", "Latn", sort_key = { from = {"æ", "ø", "å"}, to = {"z" .. p[1], "z" .. p[2], "z" .. p[3]} } } m["km"] = { "Khmer", 9205, "mkh-kmr", "Khmr", ancestors = "xhm", translit = "km-translit", --This might yield unwanted result unless its entry has {{km-IPA}}. } m["kn"] = { "Kannada", 33673, "dra-kan", "Knda, Tutg", ancestors = "dra-mkn", -- Knda translit in [[Module:scripts/data]] } m["ko"] = { "Korean", 9176, "qfa-kor", "Kore, Brai", ancestors = "ko-ear", translit = { Kore = "ko-translit", }, -- Kore strip_diacritics in [[Module:scripts/data]] } m["kr"] = { "Kanuri", 36094, "ssa-sah", "Latn, Arab", -- the sortkey and strip_diacritics are only for standard Kanuri; when dialectal entries get added, someone will have to work out how the dialects should be represented orthographically strip_diacritics = { Latn = {remove_diacritics = c.grave .. c.acute .. c.circ .. c.breve} }, sort_key = { Latn = { from = {"ǝ", "ny", "ɍ", "sh"}, to = {"e" .. p[1], "n" .. p[1], "r" .. p[1], "s" .. p[1]} }, }, } m["ks"] = { "Kashmiri", 33552, "inc-kas", "Aran, Deva, Shrd, Latn", translit = { Aran = "ks-Aran-translit", Deva = "ks-Deva-translit", -- Shrd translit in [[Module:scripts/data]] }, } -- "kv" is treated as "koi", "kpv", see [[WT:LT]] m["kw"] = { "Cornish", 25289, "cel-brs", "Latn", ancestors = "cnx", sort_key = { from = {"ch"}, to = {"c" .. p[1]} }, } m["ky"] = { "Kyrgyz", 9255, "trk-kkp", "Cyrl, Latn, Arab", translit = { Cyrl = "ky-translit" }, override_translit = true, sort_key = { Cyrl = { from = {"ё", "ң", "ө", "ү"}, to = {"е" .. p[1], "н" .. p[1], "о" .. p[1], "у" .. p[1]} }, }, } m["la"] = { "Latin", 397, "itc-laf", "Latn, Ital", ancestors = "itc-ola", -- Ital translit in [[Module:scripts/data]] (NOTE: formerly not present, probably an accidental omission) display_text = { Latn = s["itc-Latn-displaytext"] }, strip_diacritics = { Latn = s["itc-Latn-stripdiacritics"] }, sort_key = { Latn = s["itc-Latn-sortkey"] }, standard_chars = { Latn = "AaBbCcDdEeFfGgHhIiLlMmNnOoPpQqRrSsTtUuVvXx", c.punc }, } m["lb"] = { "Luxembourgish", 9051, "gmw-hgm", "Latn, Brai", ancestors = "gmw-cfr", sort_key = { Latn = { from = {"ä", "ë", "é"}, to = {"z" .. p[1], "z" .. p[2], "z" .. p[3]} }, }, } m["lg"] = { "Luganda", 33368, "bnt-nyg", "Latn", strip_diacritics = {remove_diacritics = c.acute .. c.circ}, sort_key = { from = {"ŋ"}, to = {"n" .. p[1]} }, } m["li"] = { "Limburgish", 102172, "gmw-frk", "Latn", ancestors = "dum", } m["ln"] = { "Lingala", 36217, "bnt-bmo", "Latn", sort_key = { remove_diacritics = c.acute .. c.circ .. c.caron, from = {"ɛ", "gb", "mb", "mp", "nd", "ng", "nk", "ns", "nt", "ny", "nz", "ɔ"}, to = {"e" .. p[1], "g" .. p[1], "m" .. p[1], "m" .. p[2], "n" .. p[1], "n" .. p[2], "n" .. p[3], "n" .. p[4], "n" .. p[5], "n" .. p[6], "n" .. p[7], "o" .. p[1]} }, } m["lo"] = { "Lao", 9211, "tai-swe", "Laoo", -- also Tai Noi/Lao Buhan script translit = "lo-translit", sort_key = "Laoo-sortkey", standard_chars = "0-9ກຂຄງຈຊຍດຕຖທນບປຜຝພຟມຢຣລວສຫອຮຯ-ໝ" .. c.punc, } m["lt"] = { "Lithuanian", 9083, "bat-eas", "Latn", ancestors = "olt", display_text = "lt-common", strip_diacritics = "lt-common", sort_key = "lt-common", standard_chars = "AaĄąBbCcČčDdEeĘęĖėFfGgHhIiĮįYyJjKkLlMmNnOoPpRrSsŠšTtUuŲųŪūVvZzŽž" .. c.punc, } m["lu"] = { "Luba-Katanga", 36157, "bnt-lub", "Latn", } m["lv"] = { "Latvian", 9078, "bat-eas", "Latn", strip_diacritics = { -- This attempts to convert vowels with tone marks to vowels either with or without macrons. Specifically, there should be no macrons if the vowel is part of a diphthong (including resonant diphthongs such pìrksts -> pirksts not #pīrksts). What we do is first convert the vowel + tone mark to a vowel + tilde in a decomposed fashion, then remove the tilde in diphthongs, then convert the remaining vowel + tilde sequences to macroned vowels, then delete any other tilde. We leave already-macroned vowels alone: Both e.g. ar and ār occur before consonants. FIXME: This still might not be sufficient. from = {"([Ee])" .. c.cedilla, "[" .. c.grave .. c.circ .. c.tilde .."]", "([aAeEiIoOuU])" .. c.tilde .."?([lrnmuiLRNMUI])" .. c.tilde .. "?([^aAeEiIoOuU])", "([aAeEiIoOuU])" .. c.tilde .."?([lrnmuiLRNMUI])" .. c.tilde .."?$", "([iI])" .. c.tilde .. "?([eE])" .. c.tilde .. "?", "([aAeEiIuU])" .. c.tilde, c.tilde}, to = {"%1", c.tilde, "%1%2%3", "%1%2", "%1%2", "%1" .. c.macron} }, sort_key = { from = {"ā", "č", "ē", "ģ", "ī", "ķ", "ļ", "ņ", "š", "ū", "ž"}, to = {"a" .. p[1], "c" .. p[1], "e" .. p[1], "g" .. p[1], "i" .. p[1], "k" .. p[1], "l" .. p[1], "n" .. p[1], "s" .. p[1], "u" .. p[1], "z" .. p[1]} }, standard_chars = "AaĀāBbCcČčDdEeĒēFfGgĢģHhIiĪīJjKkĶķLlĻļMmNnŅņOoPpRrSsŠšTtUuŪūVvZzŽž" .. c.punc, } m["mg"] = { "Malagasy", 7930, "poz-bre", "Latn, Arab", } m["mh"] = { "Marshallese", 36280, "poz-mic", "Latn", sort_key = { from = {"ā", "ļ", "m̧", "ņ", "n̄", "o̧", "ō", "ū"}, to = {"a" .. p[1], "l" .. p[1], "m" .. p[1], "n" .. p[1], "n" .. p[2], "o" .. p[1], "o" .. p[2], "u" .. p[1]} }, } m["mi"] = { "Māori", 36451, "poz-pep", "Latn", sort_key = { remove_diacritics = c.macron, from = {"ng", "wh"}, to = {"n" .. p[1], "w" .. p[1]} }, } m["mk"] = { "Macedonian", 9296, "zls", "Cyrl, Polyt", ancestors = "cu", translit = { Cyrl = "mk-translit", -- FIXME: formerly no translit specified for Polyt; unclear if the default [[Module:grc-translit]] is -- acceptable, so we disable it for now Polyt = false, }, strip_diacritics = { Cyrl = { remove_diacritics = c.acute, remove_exceptions = {"Ѓ", "ѓ", "Ќ", "ќ"} }, }, sort_key = { Cyrl = { remove_diacritics = c.grave, remove_exceptions = {"ѓ", "ќ"}, from = {"ѓ", "ѕ", "ј", "љ", "њ", "ќ", "џ"}, to = {"д" .. p[1], "з" .. p[1], "и" .. p[1], "л" .. p[1], "н" .. p[1], "т" .. p[1], "ч" .. p[1]} }, }, -- Polyt display_text, strip_diacritics, sort_key in [[Module:scripts/data]] standard_chars = { Cyrl = "АаБбВвГгДдЃѓЕеЖжЗзЅѕИиЈјКкЛлЉљМмНнЊњОоПпРрСсТтЌќУуФфХхЦцЧчЏџШш", c.punc }, } m["ml"] = { "Malayalam", 36236, "dra-mal", "Mlym", override_translit = true, -- Mlym translit in [[Module:scripts/data]] } m["mn"] = { "Mongolian", 9246, "xgn-cen", "Cyrl, Mong, Latn, Brai", ancestors = "cmg", translit = { Cyrl = "mn-translit", -- Mong translit in [[Module:scripts/data]] }, override_translit = true, -- Mong display_text and strip_diacritics in [[Module:scripts/data]] strip_diacritics = { Cyrl = {remove_diacritics = c.grave .. c.acute}, }, sort_key = { Cyrl = { remove_diacritics = c.grave, from = {"ё", "ө", "ү"}, to = {"е" .. p[1], "о" .. p[1], "у" .. p[1]} }, }, standard_chars = { Cyrl = "АаБбВвГгДдЕеЁёЖжЗзИиЙйЛлМмНнОоӨөРрСсТтУуҮүХхЦцЧчШшЫыЬьЭэЮюЯя—", Brai = c.braille, c.punc }, } -- "mo" is treated as "ro", see [[WT:LT]] m["mr"] = { "Marathi", 1571, "inc-sou", "Deva, Modi", ancestors = "omr", translit = { Deva = "mr-translit", Modi = "mr-Modi-translit", }, strip_diacritics = { Deva = { from = {"च़", "ज़", "झ़"}, to = {"च", "ज", "झ"} }, }, } m["ms"] = { "Malay", 9237, "poz-mly", "Latn, Arab", ancestors = "ms-cla", standard_chars = { Latn = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz", c.punc }, } m["mt"] = { "Maltese", 9166, "sem-arb", "Latn", display_text = { from = {"'"}, to = {"’"} }, strip_diacritics = { from = {"’"}, to = {"'"}, }, ancestors = "sqr", sort_key = { from = { "ċ", "ġ", "ż", -- Convert into PUA so that decomposed form does not get caught by the next step. "([cgz])", -- Ensure "c" comes after "ċ", "g" comes after "ġ" and "z" comes after "ż". "g" .. p[1] .. "ħ", -- "għ" after initial conversion of "g". p[3], p[4], "ħ", "ie", p[5] -- Convert "ċ", "ġ", "ħ", "ie", "ż" into final output. }, to = { p[3], p[4], p[5], "%1" .. p[1], "g" .. p[2], "c", "g", "h" .. p[1], "i" .. p[1], "z" } }, } m["my"] = { "Burmese", 9228, "tbq-brm", "Mymr", ancestors = "obr", translit = "my-translit", override_translit = true, sort_key = { from = {"ျ", "ြ", "ွ", "ှ", "ဿ"}, to = {"္ယ", "္ရ", "္ဝ", "္ဟ", "သ္သ"} }, } m["na"] = { "Nauruan", 13307, "poz-mic", "Latn", } m["nb"] = { "Norwegian Bokmål", 25167, "gmq", "Latn", wikimedia_codes = "no", ancestors = "gmq-mno, da", -- da as an (but not the) ancestor of nb was agreed on - do not change without discussion sort_key = s["no-sortkey"], standard_chars = s["no-standardchars"], } m["nd"] = { "Northern Ndebele", 35613, "bnt-ngu", "Latn", strip_diacritics = {remove_diacritics = c.grave .. c.acute .. c.circ .. c.macron .. c.caron}, } m["ne"] = { "Nepali", 33823, "inc-pae", "Deva, Newa", translit = { Deva = "ne-translit" }, } m["ng"] = { "Ndonga", 33900, "bnt-ova", "Latn", } m["nl"] = { "Dutch", 7411, "gmw-frk", "Latn, Brai", ancestors = "dum", sort_key = { Latn = {remove_diacritics = c.grave .. c.acute .. c.circ .. c.tilde .. c.diaer .. c.ringabove .. c.cedilla .. "'"}, }, standard_chars = { Latn = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzÄäËëÏïÖöÜü", Brai = c.braille, c.punc }, } m["nn"] = { "Norwegian Nynorsk", 25164, "gmq-wes", "Latn", ancestors = "gmq-mno", strip_diacritics = { remove_diacritics = c.grave .. c.acute, }, sort_key = s["no-sortkey"], standard_chars = s["no-standardchars"], } m["no"] = { "Norwegian", 9043, "gmq-wes", "Latn", ancestors = "gmq-mno", sort_key = s["no-sortkey"], standard_chars = s["no-standardchars"], } m["nr"] = { "Southern Ndebele", 36785, "bnt-ngu", "Latn", strip_diacritics = {remove_diacritics = c.grave .. c.acute .. c.circ .. c.macron .. c.caron}, } m["nv"] = { "Navajo", 13310, "apa", "Latn, Brai", sort_key = { remove_diacritics = c.acute .. c.ogonek, from = { "chʼ", "tłʼ", "tsʼ", -- 3 chars "ch", "dl", "dz", "gh", "hw", "kʼ", "kw", "sh", "tł", "ts", "zh", -- 2 chars "ł", "ʼ" -- 1 char }, to = { "c" .. p[2], "t" .. p[2], "t" .. p[4], "c" .. p[1], "d" .. p[1], "d" .. p[2], "g" .. p[1], "h" .. p[1], "k" .. p[1], "k" .. p[2], "s" .. p[1], "t" .. p[1], "t" .. p[3], "z" .. p[1], "l" .. p[1], "z" .. p[2] } }, } m["ny"] = { "Chichewa", 33273, "bnt-nys", "Latn", strip_diacritics = {remove_diacritics = c.acute .. c.circ}, sort_key = { from = {"ng'"}, to = {"ng"} }, } m["oc"] = { "Occitan", 14185, "roa-ocr", "Latn, Hebr", ancestors = "pro", sort_key = { Latn = { remove_diacritics = c.grave .. c.acute .. c.diaer .. c.cedilla, from = {"([lns])·h"}, to = {"%1h"} }, }, -- Hebr display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["oj"] = { "Ojibwe", 33875, "alg", "Cans, Latn", sort_key = { Latn = { from = {"aa", "ʼ", "ii", "oo", "sh", "zh"}, to = {"a" .. p[1], "h" .. p[1], "i" .. p[1], "o" .. p[1], "s" .. p[1], "z" .. p[1]} }, }, } m["om"] = { "Oromo", 33864, "cus-eas", "Latn, Ethi", } m["or"] = { "Odia", 33810, "inc-eas", "Orya", ancestors = "inc-mor", translit = "or-translit", } m["os"] = { "Ossetian", 33968, "xsc-sar", "Cyrl, Geor, Latn", ancestors = "oos", translit = { Cyrl = "os-translit", -- Geor translit in [[Module:scripts/data]] }, override_translit = true, display_text = { Cyrl = { from = {"æ", "Æ"}, to = {"ӕ", "Ӕ"} }, Latn = { from = {"ӕ", "Ӕ"}, to = {"æ", "Æ"} }, }, strip_diacritics = { Cyrl = { remove_diacritics = c.grave .. c.acute, from = {"æ", "Æ"}, to = {"ӕ", "Ӕ"} }, Latn = { from = {"ӕ", "Ӕ"}, to = {"æ", "Æ"} }, }, sort_key = { Cyrl = { from = {"ӕ", "гъ", "дж", "дз", "ё", "къ", "пъ", "тъ", "хъ", "цъ", "чъ"}, to = {"а" .. p[1], "г" .. p[1], "д" .. p[1], "д" .. p[2], "е" .. p[1], "к" .. p[1], "п" .. p[1], "т" .. p[1], "х" .. p[1], "ц" .. p[1], "ч" .. p[1]} }, }, } m["pa"] = { "Punjabi", 58635, "inc-pan", "Guru, Aran", translit = { Guru = "Guru-translit", Aran = "pa-Aran-translit", }, strip_diacritics = { Aran = { remove_diacritics = c.fathatan .. c.dammatan .. c.kasratan .. c.fatha .. c.damma .. c.kasra .. c.shadda .. c.sukun .. c.nunghunna, from = {"ݨ", "ࣇ"}, to = {"ن", "ل"} }, }, } m["pi"] = { "Pali", 36727, "inc-mid", "Latn, Brah, Deva, Beng, Sinh, Mymr, Thai, Lana, Laoo, Khmr, Cakm", --and also Khom ancestors = "sa", translit = { -- Brah translit in [[Module:scripts/data]] Deva = "sa-translit", Beng = "pi-translit", Sinh = "si-translit", Mymr = "pi-translit", Thai = "pi-translit", Lana = "pi-translit", Laoo = "pi-translit", Khmr = "pi-translit", Cakm = "Cakm-translit", }, strip_diacritics = { Thai = { from = {"ึ", u(0xF700), u(0xF70F)}, -- FIXME: Not clear what's going on with the PUA characters here. to = {"ิํ", "ฐ", "ญ"} }, Mymr = { remove_diacritics = c.VS01, }, }, sort_key = { -- FIXME: This needs to be converted into the current standardized format. from = {"ā", "ī", "ū", "ḍ", "ḷ", "m[" .. c.dotabove .. c.dotbelow .. "]", "ṅ", "ñ", "ṇ", "ṭ", "ॐ", "([เโ])([ก-ฮ])", "([ເໂ])([ກ-ຮ])", "ᩔ", "ᩕ", "ᩖ", "ᩘ", "([ᨭ-ᨱ])ᩛ", "([ᨷ-ᨾ])ᩛ", "ᩤ", u(0xFE00), u(0x200D)}, to = {"a~", "i~", "u~", "d~", "l~", "m~", "n~", "n~~", "n~~~", "t~", "ओँ", "%2%1", "%2%1", "ᩈ᩠ᩈ", "᩠ᩁ", "᩠ᩃ", "ᨦ᩠", "%1᩠ᨮ", "%1᩠ᨻ", "ᩣ"} }, } m["pl"] = { "Polish", 809, "zlw-lch", "Latn", ancestors = "zlw-mpl", sort_key = { from = {"ą", "ć", "ę", "ł", "ń", "ó", "ś", "ź", "ż"}, to = {"a" .. p[1], "c" .. p[1], "e" .. p[1], "l" .. p[1], "n" .. p[1], "o" .. p[1], "s" .. p[1], "z" .. p[1], "z" .. p[2]} }, standard_chars = "AaĄąBbCcĆćDdEeĘęFfGgHhIiJjKkLlŁłMmNnŃńOoÓóPpRrSsŚśTtUuWwYyZzŹźŻż" .. c.punc, } m["ps"] = { "Pashto", 58680, "ira-pat", "Arab", strip_diacritics = {remove_diacritics = c.fathatan .. c.dammatan .. c.kasratan .. c.fatha .. c.damma .. c.kasra .. c.shadda .. c.sukun .. c.zwarakay .. c.superalef}, } m["pt"] = { "Portuguese", 5146, "roa-gap", "Latn, Brai", sort_key = { Latn = { remove_diacritics = c.grave .. c.acute .. c.circ .. c.tilde .. c.macron .. c.diaer .. c.cedilla, from = {"ª", "æ", "º", "œ"}, to = {"a", "ae", "o", "oe"} }, }, standard_chars = { Latn = "AaÁáÂâÃãBbCcÇçDdEeÉéÊêFfGgHhIiÍíJjLlMmNnOoÓóÔôÕõPpQqRrSsTtUuÚúVvXxZz", Brai = c.braille, c.punc }, } m["qu"] = { "Quechua", 5218, "qwe", "Latn", } m["rm"] = { "Romansh", 13199, "roa-rhe", ancestors = "rm-old", "Latn", sort_key = {remove_diacritics = c.grave .. c.acute .. c.circ .. c.diaer .. c.small_e}, } m["ro"] = { "Romanian", 7913, "roa-eas", "Latn, Cyrl, Cyrs", translit = { Cyrl = "ro-translit" }, display_text = { Latn = { from = {"'", "ţ", "Ţ"}, to = {"’", "ț", "Ț"} }, }, strip_diacritics = { Latn = { from = {"’", "ţ", "Ţ"}, to = {"'", "ț", "Ț"} }, }, sort_key = { Latn = { remove_diacritics = c.grave .. c.acute, from = {"ă", "â", "î", "ș", "[țţ]"}, to = {"a" .. p[1], "a" .. p[2], "i" .. p[1], "s" .. p[1], "t" .. p[1]} }, Cyrl = { from = {"ӂ"}, to = {"ж" .. p[1]} }, }, -- Cyrs strip_diacritics, sort_key in [[Module:scripts/data]]; presumably not present standard_chars = { Latn = "AaĂăÂâBbCcDdEeFfGgHhIiÎîJjLlMmNnOoPpRrSsȘșTtȚțUuVvXxZz", Cyrl = "АаБбВвГгДдЕеЖжӁӂЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЫыЬьЭэЮюЯя", c.punc }, } m["ru"] = { "Russian", 7737, "zle", "Cyrl, Brai", ancestors = "zle-mru", translit = { Cyrl = "ru-translit" }, display_text = { Cyrl = { from = {"'"}, to = {"’"} }, }, strip_diacritics = { Cyrl = { remove_diacritics = c.grave .. c.acute .. c.diaer, remove_exceptions = {"Ё", "ё", "Ѣ̈", "ѣ̈", "Я̈", "я̈"}, from = {"’"}, to = {"'"}, }, }, sort_key = { Cyrl = { remove_diacritics = c.grave .. c.acute .. c.diaer, from = { "і", "ѣ", "ѳ", "ѵ" }, to = { "и" .. p[1], "ь" .. p[1], "я" .. p[2], "я" .. p[3] } }, }, standard_chars = { Cyrl = "АаБбВвГгДдЕеЁёЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭэЮюЯя—", Brai = c.braille, (c.punc:gsub("'", "")) -- Exclude apostrophe. }, } m["rw"] = { "Rwanda-Rundi", 3217514, "bnt-glb", "Latn", strip_diacritics = {remove_diacritics = c.acute .. c.circ .. c.macron .. c.caron}, } m["sa"] = { "Sanskrit", 11059, "inc", "as-Beng, Bali, Beng, Bhks, Brah, Mymr, xwo-Mong, Deva, Gujr, Guru, Gran, Hani, Java, Kthi, Knda, Kawi, Khar, Khmr, Laoo, Mlym, mnc-Mong, Marc, Modi, Mong, Nand, Newa, Orya, Phag, Ranj, Saur, Shrd, Sidd, Sinh, Soyo, Lana, Takr, Taml, Tang, Telu, Thai, Tibt, Tutg, Tirh, Zanb", --and also Khom; script codes sorted by canonical name rather than code for [[MOD:sa-convert]] translit = { Beng = "sa-Beng-translit", ["as-Beng"] = "sa-Beng-translit", -- Brah translit in [[Module:scripts/data]] Deva = "sa-translit", Gujr = "sa-Gujr-translit", Guru = "sa-Guru-translit", Java = "sa-Java-translit", Kthi = "sa-Kthi-translit", Khmr = "pi-translit", Knda = "sa-Knda-translit", Lana = "pi-translit", Laoo = "pi-translit", Mlym = "sa-Mlym-translit", Modi = "sa-Modi-translit", -- Mong, mnc-Mong, xwo-Mong translit in [[Module:scripts/data]] -- NOTE: Formerly used xal-translit for transliterating xwo-Mong but that only handles Cyrillic; it has -- code to transliterate xwo-Mong but it's broken so I've replaced it with the default xwo-translit. Mymr = "pi-translit", Orya = "sa-Orya-translit", -- Shrd translit in [[Module:scripts/data]] -- Sidd translit in [[Module:scripts/data]] Sinh = "si-translit", Taml = "sa-Taml-translit", Telu = "sa-Telu-translit", Thai = "pi-translit", -- Tibt translit in [[Module:scripts/data]] }, -- Mong display_text and strip_diacritics in [[Module:scripts/data]] -- Tibt display_text, strip_diacritics, sort_key in [[Module:scripts/data]] strip_diacritics = { Deva = s["sa-Deva-stripdiacritics"], Mymr = { remove_diacritics = c.VS01, }, Thai = { from = {"ึ", u(0xF700), u(0xF70F)}, -- FIXME: Not clear what's going on with the PUA characters here. to = {"ิํ", "ฐ", "ญ"} }, }, sort_key = { Deva = s["sa-Deva-stripdiacritics"], -- until we have a proper Sanskrit sorting algorithm. Lana = { -- Tai Tham from = {"ᩔ", "ᩕ", "ᩖ", "ᩘ", "([ᨭ-ᨱ])ᩛ", "([ᨷ-ᨾ])ᩛ", "ᩤ"}, to = {"ᩈ᩠ᩈ", "᩠ᩁ", "᩠ᩃ", "ᨦ᩠", "%1᩠ᨮ", "%1᩠ᨻ", "ᩣ"}, }, Laoo = "Laoo-sortkey", Latn = { from = {"ā", "ī", "ū", "ḍ", "ḷ", "ḹ", "m[" .. c.dotabove .. c.dotbelow .. "]", "ṅ", "ñ", "ṇ", "ṛ", "ṝ", "ś", "ṣ", "ṭ"}, to = {"a~", "i~", "u~", "d~", "l~", "l~~", "m~", "n~", "n~~", "n~~~", "r~", "r~~", "s~", "s~~", "t~"}, }, Mymr = { remove_diacritics = c.VS01, }, Thai = "Thai-sortkey", -- FIXME: The previous sort key which mixed all scripts removed ZWJ; I don't know which script(s) this was -- intended for and there are no other languages which remove it in the sort key AFAIK. If it needs to be -- removed, specify the script(s) it needs to be removed under or add handling for the "all" script that applies -- regardless of script. --all = { -- remove_diacritics = c.ZWJ, --}, }, } m["sc"] = { "Sardinian", 33976, "roa-sou", "Latn", ancestors = "sc-old", } m["sd"] = { "Sindhi", 33997, "inc-snd", "Arab, Deva, Sind, Khoj", translit = { Sind = "Sind-translit", Arab = "sd-Arab-translit" }, strip_diacritics = { Arab = { remove_diacritics = c.kashida .. c.fathatan .. c.dammatan .. c.kasratan .. c.fatha .. c.damma .. c.kasra .. c.shadda .. c.sukun .. c.superalef, from = {"ٱ"}, to = {"ا"} }, }, } m["se"] = { "Northern Sami", 33947, "smi", "Latn", display_text = { from = {"'"}, to = {"ˈ"} }, strip_diacritics = {remove_diacritics = c.macron .. c.dotbelow .. "'ˈ"}, sort_key = { from = {"á", "č", "đ", "ŋ", "š", "ŧ", "ž"}, to = {"a" .. p[1], "c" .. p[1], "d" .. p[1], "n" .. p[1], "s" .. p[1], "t" .. p[1], "z" .. p[1]} }, standard_chars = "AaÁáBbCcČčDdĐđEeFfGgHhIiJjKkLlMmNnŊŋOoPpRrSsŠšTtŦŧUuVvZzŽž" .. c.punc, } m["sg"] = { "Sango", 33954, "crp", "Latn", ancestors = "ngb", } m["sh"] = { "Serbo-Croatian", 9301, "zls", "Latn, Cyrl, Glag, Arab", ietf_subtag = "hbs", -- ISO 639-3 code, since "sh" is deprecated from ISO 639-1 wikimedia_codes = "sh, bs, hr, sr", strip_diacritics = { Latn = { remove_diacritics = c.grave .. c.acute .. c.tilde .. c.macron .. c.dgrave .. c.invbreve, remove_exceptions = {"Ć", "ć", "Ś", "ś", "Ź", "ź"} }, Cyrl = { remove_diacritics = c.grave .. c.acute .. c.tilde .. c.macron .. c.dgrave .. c.invbreve, remove_exceptions = {"З́", "з́", "С́", "с́"} }, }, sort_key = { Latn = { remove_diacritics = c.grave .. c.acute .. c.tilde .. c.macron .. c.dgrave .. c.invbreve, remove_exceptions = {"ć", "ś", "ź"}, from = {"č", "ć", "dž", "đ", "lj", "nj", "š", "ś", "ž", "ź"}, to = {"c" .. p[1], "c" .. p[2], "d" .. p[1], "d" .. p[2], "l" .. p[1], "n" .. p[1], "s" .. p[1], "s" .. p[2], "z" .. p[1], "z" .. p[2]} }, Cyrl = { remove_diacritics = c.grave .. c.acute .. c.tilde .. c.macron .. c.dgrave .. c.invbreve, remove_exceptions = {"з́", "с́"}, from = {"ђ", "з́", "ј", "љ", "њ", "с́", "ћ", "џ"}, to = {"д" .. p[1], "з" .. p[1], "и" .. p[1], "л" .. p[1], "н" .. p[1], "с" .. p[1], "т" .. p[1], "ч" .. p[1]} }, }, standard_chars = { Latn = "AaBbCcČčĆćDdĐđEeFfGgHhIiJjKkLlMmNnOoPpRrSsŠšTtUuVvZzŽž", Cyrl = "АаБбВвГгДдЂђЕеЖжЗзИиЈјКкЛлЉљМмНнЊњОоПпРрСсТтЋћУуФфХхЦцЧчЏџШш", c.punc }, } m["si"] = { "Sinhalese", 13267, "inc-ins", "Sinh", translit = "si-translit", override_translit = true, } m["sk"] = { "Slovak", 9058, "zlw", "Latn", ancestors = "zlw-osk", sort_key = {remove_diacritics = c.acute .. c.circ .. c.diaer .. c.caron}, standard_chars = "AaÁáÄäBbCcČčDdĎďEeÉéFfGgHhIiÍíJjKkLlĹ弾MmNnŇňOoÓóÔôPpRrŔŕSsŠšTtŤťUuÚúVvYyÝýZzŽž" .. c.punc, } m["sl"] = { "Slovene", 9063, "zls", "Latn", strip_diacritics = { remove_diacritics = c.grave .. c.acute .. c.circ .. c.macron .. c.dgrave .. c.invbreve .. c.dotbelow, remove_exceptions = {"Ć", "ć", "Ǵ", "ǵ", "Ś", "ś", "Ź", "ź"}, from = {"Ə", "ə", "Ł", "ł"}, to = {"E", "e", "L", "l"}, }, sort_key = { remove_diacritics = c.grave .. c.acute .. c.circ .. c.tilde .. c.macron .. c.dotabove .. c.ringabove .. c.dgrave .. c.invbreve .. c.dotbelow .. c.ringbelow .. c.ogonek, remove_exceptions = {"ć", "ǵ", "ś", "ź"}, from = {"ä", "č", "ć", "đ", "ə", "ë", "ǧ", "ǵ", "ï", "ł", "ö", "š", "ś", "ü", "ž", "ź"}, to = {"a" .. p[1], "c" .. p[1], "c" .. p[2], "d" .. p[1], "e", "e" .. p[1], "g" .. p[1], "g" .. p[2], "i" .. p[1], "l", "o" .. p[1], "s" .. p[1], "s" .. p[2], "u" .. p[1], "z" .. p[1], "z" .. p[2]}, }, standard_chars = "AaBbCcČčDdEeFfGgHhIiJjKkLlMmNnOoPpRrSsŠšTtUuVvZzŽž" .. c.punc, } m["sm"] = { "Samoan", 34011, "poz-pnp", "Latn", } m["sn"] = { "Shona", 34004, "bnt-sho", "Latn", strip_diacritics = {remove_diacritics = c.acute}, } m["so"] = { "Somali", 13275, "cus-som", "Latn, Arab, Osma", strip_diacritics = { Latn = {remove_diacritics = c.grave .. c.acute .. c.circ} }, } m["sq"] = { "Albanian", 8748, "sqj", "Latn, Grek, Arab, Elba, Todr, Vith", translit = { Elba = "Elba-translit", Vith = "Vith-translit", }, -- Grek display_text, strip_diacritics, sort_key in [[Module:scripts/data]] strip_diacritics = { Latn = { remove_diacritics = c.acute .. c.circ .. c.macron, from = {'^[ie] (%w)', '^të (%w)'}, to = {'%1', '%1'}, }, }, sort_key = { Latn = { remove_diacritics = c.acute .. c.circ .. c.macron .. c.tilde .. c.breve .. c.caron, from = {'^[ie] (%w)', '^të (%w)', 'ç', 'dh', 'ë', 'gj', 'll', 'nj', 'rr', 'sh', 'th', 'xh', 'zh'}, to = {'%1', '%1', 'c'..p[1], 'd'..p[1], 'e'..p[1], 'g'..p[1], 'l'..p[1], 'n'..p[1], 'r'..p[1], 's'..p[1], 't'..p[1], 'x'..p[1], 'z'..p[1]}, } -- TODO: Grek if the default sort key is unsuitable }, standard_chars = { Latn = "AaBbCcÇçDdEeËëFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvXxYyZz", c.punc }, } m["ss"] = { "Swazi", 34014, "bnt-ngu", "Latn", strip_diacritics = {remove_diacritics = c.grave .. c.acute .. c.circ .. c.macron .. c.caron}, } m["st"] = { "Sotho", 34340, "bnt-sts", "Latn", strip_diacritics = {remove_diacritics = c.grave .. c.acute .. c.circ .. c.macron .. c.caron}, } m["su"] = { "Sundanese", 34002, "poz-msa", "Latn, Sund, Arab", ancestors = "osn", translit = { Sund = "Sund-translit" }, } m["sv"] = { "Swedish", 9027, "gmq-eas", "Latn", ancestors = "gmq-osw-lat", sort_key = { remove_diacritics = c.grave .. c.acute .. c.circ .. c.tilde .. c.macron .. c.dacute .. c.caron .. c.cedilla .. "':", remove_exceptions = {"å"}, from = {"ø", "æ", "œ", "ß", "ꜩ", "å", "aͤ", "oͤ"}, to = {"ö", "ae", "oe", "ss", "tz", "z" .. p[1], "ä", "ö"} }, standard_chars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpRrSsTtUuVvXxYyÅåÄäÖö" .. c.punc, } m["sw"] = { "Swahili", 7838, "bnt-swh", "Latn, Arab", sort_key = { Latn = { from = {"ng'"}, to = {"ng" .. p[1]} }, }, } m["ta"] = { "Tamil", 5885, "dra-tam", "Taml", ancestors = "ta-mid", translit = "ta-translit", override_translit = true, } m["te"] = { "Telugu", 8097, "dra-tel", "Telu", translit = "te-translit", override_translit = true, } m["tg"] = { "Tajik", 9260, "ira-swi", "Cyrl, Arab, Latn", ancestors = "fa-cls", translit = { Cyrl = "tg-translit" }, override_translit = true, strip_diacritics = { Cyrl = s["tg-stripdiacritics"], Latn = s["tg-stripdiacritics"], }, sort_key = { Cyrl = { from = {"ғ", "ё", "ӣ", "қ", "ӯ", "ҳ", "ҷ"}, to = {"г" .. p[1], "е" .. p[1], "и" .. p[1], "к" .. p[1], "у" .. p[1], "х" .. p[1], "ч" .. p[1]} }, }, } m["th"] = { "Thai", 9217, "tai-swe", "Thai, Khomt, Brai", translit = { Thai = "th-translit" }, sort_key = { Thai = "Thai-sortkey" }, } m["ti"] = { "Tigrinya", 34124, "sem-eth", "Ethi", translit = "Ethi-translit", } m["tk"] = { "Turkmen", 9267, "trk-ogz", "Latn, Cyrl, Arab", strip_diacritics = { Latn = s["tk-stripdiacritics"], Cyrl = s["tk-stripdiacritics"], }, sort_key = { Latn = { from = {"ç", "ä", "ž", "ň", "ö", "ş", "ü", "ý"}, to = {"c" .. p[1], "e" .. p[1], "j" .. p[1], "n" .. p[1], "o" .. p[1], "s" .. p[1], "u" .. p[1], "y" .. p[1]} }, Cyrl = { from = {"ё", "җ", "ң", "ө", "ү", "ә"}, to = {"е" .. p[1], "ж" .. p[1], "н" .. p[1], "о" .. p[1], "у" .. p[1], "э" .. p[1]} }, }, ancestors = "trk-eog", } m["tl"] = { "Tagalog", 34057, "phi", "Latn, Tglg", translit = { Tglg = "tl-translit" }, override_translit = true, strip_diacritics = { Latn = {remove_diacritics = c.grave .. c.acute .. c.circ} }, standard_chars = { Latn = "AaBbKkDdEeGgHhIiLlMmNnOoPpRrSsTtUuWwYy", c.punc }, sort_key = { Latn = "tl-sortkey", }, } m["tn"] = { "Tswana", 34137, "bnt-sts", "Latn", } m["to"] = { "Tongan", 34094, "poz-ton", "Latn", strip_diacritics = {remove_diacritics = c.acute}, sort_key = {remove_diacritics = c.macron}, } m["tr"] = { "Turkish", 256, "trk-ogz", "Latn", ancestors = "ota", dotted_dotless_i = true, sort_key = { from = { -- Ignore circumflex, but account for capital Î wrongly becoming ı + circ due to dotted dotless I logic. "ı" .. c.circ, c.circ, "i", -- Ensure "i" comes after "ı". "ç", "ğ", "ı", "ö", "ş", "ü" }, to = { "i", "", "i" .. p[1], "c" .. p[1], "g" .. p[1], "i", "o" .. p[1], "s" .. p[1], "u" .. p[1] } }, standard_chars = "AaÂâBbCcÇçDdEeFfGgĞğHhIıİiÎîJjKkLlMmNnOoÖöPpRrSsŞşTtUuÛûÜüVvYyZz" .. c.punc, } m["ts"] = { "Tsonga", 34327, "bnt-tsr", "Latn", } m["tt"] = { "Tatar", 25285, "trk-kbu", "Cyrl, Latn, Arab", translit = { Cyrl = "tt-translit", Arab = "tt-translit" }, --override_translit = true, -- enable override until Module code can detect Russian loans such as [[аэропорт]] dotted_dotless_i = true, sort_key = { Cyrl = { from = {"ә", "ў", "ғ", "ё", "җ", "қ", "ң", "ө", "ү", "һ"}, to = {"а" .. p[1], "в" .. p[1], "г" .. p[1], "е" .. p[1], "ж" .. p[1], "к" .. p[1], "н" .. p[1], "о" .. p[1], "у" .. p[1], "х" .. p[1]} }, Latn = { from = { "i", -- Ensure "i" comes after "ı". "ä", "ə", "ç", "ğ", "ı", "ñ", "ŋ", "ö", "ɵ", "ş", "ü" }, to = { "i" .. p[1], "a" .. p[1], "a" .. p[2], "c" .. p[1], "g" .. p[1], "i", "n" .. p[1], "n" .. p[2], "o" .. p[1], "o" .. p[2], "s" .. p[1], "u" .. p[1] } }, }, } -- "tw" is treated as "ak", see [[WT:LT]] m["ty"] = { "Tahitian", 34128, "poz-pep", "Latn", } m["ug"] = { "Uyghur", 13263, "trk-kar", "Arab, Latn, Cyrl", ancestors = "chg", translit = { Arab = "ug-translit", Cyrl = "ug-translit", }, override_translit = true, } m["uk"] = { "Ukrainian", 8798, "zle", "Cyrl", ancestors = "zle-muk", translit = "uk-translit", strip_diacritics = {remove_diacritics = c.grave .. c.acute}, sort_key = { remove_diacritics = c.grave .. c.acute, from = { "ї", -- 2 chars "ґ", "є", "і" -- 1 char }, to = { "и" .. p[2], "г" .. p[1], "е" .. p[1], "и" .. p[1] } }, standard_chars = "АаБбВвГгДдЕеЄєЖжЗзИиІіЇїЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЬьЮюЯя" .. c.punc:gsub("'", ""), -- Exclude apostrophe. } m["ur"] = { "Urdu", 1617, "inc-hnd", "Aran, Hebr", translit = { Aran = "ur-translit" }, strip_diacritics = { Aran = { -- character "ۂ" code U+06C2 to "ه" and "هٔ" (U+0647 + U+0654) to "ه"; hamzatu l-waṣli to a regular alif from = {"هٔ", "ۂ", "ٱ"}, to = {"ہ", "ہ", "ا"}, remove_diacritics = c.fathatan .. c.dammatan .. c.kasratan .. c.fatha .. c.damma .. c.kasra .. c.shadda .. c.sukun .. c.nunghunna .. c.superalef }, }, -- Hebr display_text, strip_diacritics, sort_key in [[Module:scripts/data]] standard_chars = { Aran = "ایببپتثجچحخدذرزژسشصضطظعغفقکگلࣇڷمنݨوؤہھئٹڈڑآے", c.punc, }, } m["uz"] = { "Uzbek", 9264, "trk-kar", "Latn, Cyrl, Arab", ancestors = "chg", translit = { Cyrl = "uz-translit" }, sort_key = { Latn = { from = {"oʻ", "gʻ", "sh", "ch", "ng"}, to = {"z" .. p[1], "z" .. p[2], "z" .. p[3], "z" .. p[4], "z" .. p[5]} }, Cyrl = { from = {"ё", "ў", "қ", "ғ", "ҳ"}, to = {"е" .. p[1], "я" .. p[1], "я" .. p[2], "я" .. p[3], "я" .. p[4]} }, }, strip_diacritics = { Arab = "ar-stripdiacritics", }, } m["ve"] = { "Venda", 32704, "bnt-bso", "Latn", } m["vi"] = { "Vietnamese", 9199, "mkh-vie", "Latn, Hani", ancestors = "mkh-mvi", sort_key = { Latn = "vi-sortkey", Hani = "Hani-sortkey", }, } m["vo"] = { "Volapük", 36986, "art", "Latn", } m["wa"] = { "Walloon", 34219, "roa-oil", "Latn", sort_key = s["roa-oil-sortkey"], } m["wo"] = { "Wolof", 34257, "alv-fwo", "Latn, Arab, Gara", } m["xh"] = { "Xhosa", 13218, "bnt-ngu", "Latn", strip_diacritics = {remove_diacritics = c.grave .. c.acute .. c.circ .. c.macron .. c.caron}, } m["yi"] = { "Yiddish", 8641, "gmw-hgm", "Hebr, Latn", ancestors = "gmh", translit = { Hebr = "yi-translit", }, -- Hebr display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["yo"] = { "Yoruba", 34311, "alv-yor", "Latn, Arab", strip_diacritics = { Latn = {remove_diacritics = c.grave .. c.acute .. c.macron} }, sort_key = { Latn = { from = {"ẹ", "ɛ", "gb", "ị", "kp", "ọ", "ɔ", "ṣ", "sh", "ụ"}, to = {"e" .. p[1], "e" .. p[1], "g" .. p[1], "i" .. p[1], "k" .. p[1], "o" .. p[1], "o" .. p[1], "s" .. p[1], "s" .. p[1], "u" .. p[1]} }, }, } m["za"] = { "Zhuang", 13216, "tai", "Latn, Hani", sort_key = { Latn = "za-sortkey", Hani = "Hani-sortkey", }, } m["zh"] = { "Chinese", 7850, "zhx", "Hants, Latn, Bopo, Nshu, Brai", ancestors = "ltc", generate_alternants = "zh-generatealternants", translit = { Hani = "zh-translit", Bopo = "zh-translit", }, sort_key = { Hani = "Hani-sortkey" }, } m["zu"] = { "Zulu", 10179, "bnt-ngu", "Latn", strip_diacritics = {remove_diacritics = c.grave .. c.acute .. c.circ .. c.macron .. c.caron}, } return require("Module:tungumál").finalizeData(m, "language") bliw0pv4f03hwpzj5og6x5a4zrrafqh Notandi:Ow! That Hurts!/íslenskaðar einingar 2 119302 393952 2026-10-09T05:35:02Z Ow! That Hurts! 8952 skapað 393952 wikitext text/x-wiki * Language — tungumál ([[Module:tungumál]]) ** Language/data — tungumál/gögn ([[Module:tungumál/gögn]]) ** Language/data/2 — tungumál/gögn/2 ([[Module:tungumál/gögn/2]]) e6aglc5k8afs45zsh8nb0m8eifauu77 393954 393952 2026-10-09T05:38:57Z Ow! That Hurts! 8952 393954 wikitext text/x-wiki * language — tungumál ([[Module:tungumál]]) ** language/data — tungumál/gögn ([[Module:tungumál/gögn]]) ** language/data/2 — tungumál/gögn/2 ([[Module:tungumál/gögn/2]]) ** language/data/exceptional — tungumál/gögn/undantekningar ([[Module:tungumál/gögn/undantekningar]]) 6hz195geh9331f6nxqons8mrtsf4wkg 393956 393954 2026-10-09T05:41:30Z Ow! That Hurts! 8952 393956 wikitext text/x-wiki * language — tungumál ([[Module:tungumál]]) ** language/data — tungumál/gögn ([[Module:tungumál/gögn]]) ** language/data/2 — tungumál/gögn/2 ([[Module:tungumál/gögn/2]]) *** language/data/2/extra — tungumál/gögn/2/extra ([[Module:tungumál/gögn/2/extra]]) ** language/data/exceptional — tungumál/gögn/undantekningar ([[Module:tungumál/gögn/undantekningar]]) 7n56l4pcab9d09hty393t1bu13rkt6r 393958 393956 2026-10-09T05:45:14Z Ow! That Hurts! 8952 393958 wikitext text/x-wiki * language — tungumál ([[Module:tungumál]]) ** language/data — tungumál/gögn ([[Module:tungumál/gögn]]) ** language/data/2 — tungumál/gögn/2 ([[Module:tungumál/gögn/2]]) *** language/data/2/extra — tungumál/gögn/2/extra ([[Module:tungumál/gögn/2/extra]]) ** language/data/3/a — tungumál/gögn/3/a ([[Module:tungumál/gögn/3/a]]) ** language/data/exceptional — tungumál/gögn/undantekningar ([[Module:tungumál/gögn/undantekningar]]) c9zsu7jhis2qibr3s0k6uqogtcqgbnl 393959 393958 2026-10-09T06:53:12Z Ow! That Hurts! 8952 Ow! That Hurts! færði [[Notandi:Ow! That Hurts!/íslenskuð undirforrit]] á [[Notandi:Ow! That Hurts!/íslenskaðar einingar]] 393958 wikitext text/x-wiki * language — tungumál ([[Module:tungumál]]) ** language/data — tungumál/gögn ([[Module:tungumál/gögn]]) ** language/data/2 — tungumál/gögn/2 ([[Module:tungumál/gögn/2]]) *** language/data/2/extra — tungumál/gögn/2/extra ([[Module:tungumál/gögn/2/extra]]) ** language/data/3/a — tungumál/gögn/3/a ([[Module:tungumál/gögn/3/a]]) ** language/data/exceptional — tungumál/gögn/undantekningar ([[Module:tungumál/gögn/undantekningar]]) c9zsu7jhis2qibr3s0k6uqogtcqgbnl Module:tungumál/gögn/undantekningar 828 119303 393953 2026-10-09T05:38:15Z Ow! That Hurts! 8952 skapað 393953 Scribunto text/plain local m_langdata = require("Module:tungumál/gögn") -- Loaded on demand, as it may not be needed (depending on the data). local function u(...) u = require("Module:string utilities").char return u(...) end local c = m_langdata.chars local p = m_langdata.puaChars local s = m_langdata.shared local m = {} m["aav-khs-pro"] = { "Proto-Khasian", 116773216, "aav-khs", "Latn", type = "reconstructed", } m["aav-nic-pro"] = { "Proto-Nicobarese", 116773793, "aav-nic", "Latn", type = "reconstructed", } m["aav-pkl-pro"] = { "Proto-Pnar-Khasi-Lyngngam", 116773259, "aav-pkl", "Latn", type = "reconstructed", } m["aav-pro"] = { -- mkh-pro will merge into this "Proto-Austroasiatic", 116773186, "aav", "Latn", type = "reconstructed", } m["afa-pro"] = { "Proto-Afroasiatic", 269125, "afa", "Latn", type = "reconstructed", } m["alg-aga"] = { "Agawam", nil, "alg-eas", "Latn", } m["alg-pro"] = { "Proto-Algonquian", 7251834, "alg", "Latn", type = "reconstructed", sort_key = {remove_diacritics = "·"}, } m["alv-ama"] = { "Amasi", 4740400, "nic-grs", "Latn", strip_diacritics = {remove_diacritics = c.grave .. c.acute .. c.circ .. c.tilde .. c.macron}, } m["alv-bgu"] = { "Bainouk Gubeeher", 17002646, "alv-bny", "Latn", } m["alv-bua-pro"] = { "Proto-Bua", 116773723, "alv-bua", "Latn", type = "reconstructed", } m["alv-cng-pro"] = { "Proto-Cangin", 116773726, "alv-cng", "Latn", type = "reconstructed", } m["alv-edo-pro"] = { "Proto-Edoid", 116773206, "alv-edo", "Latn", type = "reconstructed", } m["alv-fli-pro"] = { "Proto-Fali", 116773754, "alv-fli", "Latn", type = "reconstructed", } m["alv-gbe-pro"] = { "Proto-Gbe", 116773208, "alv-gbe", "Latn", type = "reconstructed", } m["alv-gng-pro"] = { "Proto-Guang", 116773757, "alv-gng", "Latn", type = "reconstructed", } m["alv-gtm-pro"] = { "Proto-Central Togo", 116773732, "alv-gtm", "Latn", type = "reconstructed", } m["alv-gwa"] = { "Gwara", 16945580, "nic-pla", "Latn", } m["alv-hei-pro"] = { "Proto-Heiban", 116773760, "alv-hei", "Latn", type = "reconstructed", } m["alv-ido-pro"] = { "Proto-Idomoid", 116773764, "alv-ido", "Latn", type = "reconstructed", } m["alv-igb-pro"] = { "Proto-Igboid", 116773765, "alv-igb", "Latn", type = "reconstructed", } m["alv-kwa-pro"] = { "Proto-Kwa", 116773780, "alv-kwa", "Latn", type = "reconstructed", } m["alv-mum-pro"] = { "Proto-Mumuye", 116773791, "alv-mum", "Latn", type = "reconstructed", } m["alv-nup-pro"] = { "Proto-Nupoid", 116773795, "alv-nup", "Latn", type = "reconstructed", } m["alv-pro"] = { "Proto-Atlantic-Congo", 116732838, "alv", "Latn", type = "reconstructed", } m["alv-edk-pro"] = { "Proto-Edekiri", nil, "alv-edk", "Latn", type = "reconstructed", } m["alv-yor-pro"] = { "Proto-Yoruba", nil, "alv-yor", "Latn", type = "reconstructed", } m["alv-yrd-pro"] = { "Proto-Yoruboid", 116773824, "alv-yrd", "Latn", type = "reconstructed", } m["alv-von-pro"] = { "Proto-Volta-Niger", 116773820, "alv-von", "Latn", type = "reconstructed", } m["apa-pro"] = { "Proto-Apachean", 116773135, "apa", "Latn", type = "reconstructed", } m["aql-pro"] = { "Proto-Algic", 18389588, "aql", "Latn", type = "reconstructed", sort_key = {remove_diacritics = "·"}, } m["art-adu"] = { "Adûni", 1232159, "art", "Latn", type = "appendix-constructed", } m["art-bel"] = { "Belter Creole", 108055510, "art", "Latn", type = "appendix-constructed", sort_key = { remove_diacritics = c.acute, from = {"ɒ"}, to = {"a"}, }, } m["art-blk"] = { "Bolak", 2909283, "art", "Latn", type = "appendix-constructed", } m["art-bsp"] = { "Black Speech", 686210, "art", "Latn, Teng", type = "appendix-constructed", } m["art-com"] = { "Communicationssprache", 35227, "art", "Latn", type = "appendix-constructed", } m["art-dtk"] = { "Dothraki", 2914733, "art", "Latn", type = "appendix-constructed", } m["art-elo"] = { "Eloi", nil, "art", "Latn", type = "appendix-constructed", } m["art-gld"] = { "Goa'uld", 19823, "art", "Latn, Egyp, Mero", type = "appendix-constructed", } m["art-lap"] = { "Lapine", 6488195, "art", "Latn", type = "appendix-constructed", } m["art-man"] = { "Mandalorian", 54289, "art", "Latn", type = "appendix-constructed", } m["art-mun"] = { "Mundolinco", 851355, "art", "Latn", type = "appendix-constructed", } m["art-nav"] = { "Naʼvi", 316939, "art", "Latn", type = "appendix-constructed", } m["art-vlh"] = { "High Valyrian", 64483808, "art", "Latn", type = "appendix-constructed", } m["ath-nic"] = { "Nicola", 20609, "ath-nor", "Latn", } m["ath-pro"] = { "Proto-Athabaskan", 104841722, "ath", "Latn", type = "reconstructed", } m["auf-pro"] = { "Proto-Arawan", 116773706, "auf", "Latn", type = "reconstructed", } m["aus-alu"] = { "Alungul", 16827670, "aus-pmn", "Latn", } m["aus-and"] = { "Andjingith", 4754509, "aus-pmn", "Latn", } m["aus-ang"] = { "Angkula", 16828520, "aus-pmn", "Latn", } m["aus-arn-pro"] = { "Proto-Arnhem", 116773720, "aus-arn", "Latn", type = "reconstructed", } m["aus-bra"] = { "Barranbinya", 4863220, "aus-pmn", "Latn", } m["aus-brm"] = { "Barunggam", 4865914, "aus-pmn", "Latn", } m["aus-cww-pro"] = { "Proto-Central New South Wales", 116773199, "aus-cww", "Latn", type = "reconstructed", } m["aus-dal-pro"] = { "Proto-Daly", 116773743, "aus-dal", "Latn", type = "reconstructed", } m["aus-guw"] = { "Guwar", 6652138, "aus-pam", "Latn", } m["aus-lsw"] = { "Little Swanport", 6652138, "qfa-unc", "Latn", } m["aus-mbi"] = { "Mbiywom", 6799701, "aus-pmn", "Latn", } m["aus-ngk"] = { "Ngkoth", 7022405, "aus-pmn", "Latn", } m["aus-nyu-pro"] = { "Proto-Nyulnyulan", 116773797, "aus-nyu", "Latn", type = "reconstructed", } m["aus-pam-pro"] = { "Proto-Pama-Nyungan", 33942, "aus-pam", "Latn", type = "reconstructed", } m["aus-tul"] = { "Tulua", 16938541, "aus-pam", "Latn", } m["aus-uwi"] = { "Uwinymil", 7903995, "aus-arn", "Latn", } m["aus-wdj-pro"] = { "Proto-Iwaidjan", 116773767, "aus-wdj", "Latn", type = "reconstructed", } m["aus-won"] = { "Wong-gie", nil, "aus-pam", "Latn", } m["aus-wul"] = { "Wulguru", 8039196, "aus-dyb", "Latn", } m["aus-ynk"] = { -- contrast nny "Yangkaal", 3913770, "aus-tnk", "Latn", } m["awd-amc-pro"] = { "Proto-Amuesha-Chamicuro", nil, "awd", "Latn", type = "reconstructed", } m["awd-kmp-pro"] = { "Proto-Kampa", nil, "awd", "Latn", type = "reconstructed", } m["awd-prw-pro"] = { "Proto-Paresi-Waura", nil, "awd", "Latn", type = "reconstructed", } m["awd-ama"] = { "Amarizana", 16827787, "awd", "Latn", } m["awd-ana"] = { "Anauyá", 16828252, "awd", "Latn", } m["awd-apo"] = { "Apolista", 16916645, "awd", "Latn", } m["awd-cab"] = { "Cabre", 16850160, "awd", "Latn", } m["awd-gnu"] = { "Guinau", 3504087, "awd", "Latn", } m["awd-kar"] = { "Cariay", 16920253, "awd", "Latn", } m["awd-kaw"] = { "Kawishana", 6379993, "awd-nwk", "Latn", } m["awd-kus"] = { "Kustenau", 5196293, "awd", "Latn", } m["awd-man"] = { "Manao", 6746920, "awd", "Latn", } m["awd-mar"] = { "Marawan", 6755108, "awd", "Latn", } m["awd-mpr"] = { "Maipure", 6736872, "awd", "Latn", } m["awd-mrt"] = { "Mariaté", 16910017, "awd-nwk", "Latn", } m["awd-nwk-pro"] = { "Proto-Nawiki", 116773234, "awd-nwk", "Latn", type = "reconstructed", } m["awd-pai"] = { "Paikoneka", 128807835, "awd", "Latn", } m["awd-pas"] = { "Pasé", 7143168, "awd-nwk", "Latn", } m["awd-pro"] = { "Proto-Arawak", 97573478, "awd", "Latn", type = "reconstructed", } m["awd-she"] = { "Shebayo", 7492248, "awd", "Latn", } m["awd-taa-pro"] = { "Proto-Ta-Arawak", 116773282, "awd-taa", "Latn", type = "reconstructed", } m["awd-wai"] = { "Wainumá", 16910017, "awd-nwk", "Latn", } m["awd-war"] = { "Old Warekena", 105320180, "awd-nwk", "Latn", } m["awd-yum"] = { "Yumana", 8061062, "awd-nwk", "Latn", } m["azc-caz"] = { "Cazcan", 5055514, "azc", "Latn", } m["azc-cup-pro"] = { "Proto-Cupan", 116773738, "azc-cup", "Latn", type = "reconstructed", } m["azc-ktn"] = { "Kitanemuk", 3197558, "azc-tak", "Latn", } m["azc-nah-pro"] = { "Proto-Nahuan", 7251860, "azc-nah", "Latn", type = "reconstructed", } m["azc-nic"] = { "Nicoleño", 50241488, "azc", "Latn", } m["azc-num-pro"] = { "Proto-Numic", 116773247, "azc-num", "Latn", type = "reconstructed", } m["azc-pro"] = { "Proto-Uto-Aztecan", 96400333, "azc", "Latn", type = "reconstructed", } m["azc-tak-pro"] = { "Proto-Takic", 116773283, "azc-tak", "Latn", type = "reconstructed", } m["azc-tat"] = { "Tataviam", 743736, "azc", "Latn", } m["ber-pro"] = { "Proto-Berber", 2855698, "ber", "Latn", type = "reconstructed", } m["ber-fog"] = { "Fogaha", 107610173, "ber", "Latn", } m["ber-zuw"] = { "Zuwara", 4117169, "ber", "Latn", } m["bnt-bal"] = { "Balong", 93935237, "bnt-bbo", "Latn", } m["bnt-bon"] = { "Boma Nkuu", nil, "bnt", "Latn", } m["bnt-boy"] = { "Boma Yumu", nil, "bnt", "Latn", } m["bnt-bwa"] = { "Bwala", 128810345, "bnt-tek", "Latn", } m["bnt-cmw"] = { "Chimwiini", 4958328, "bnt-swh", "Latn", } m["bnt-ind"] = { "Indanga", 51412803, "bnt", "Latn", } m["bnt-lal"] = { "Lala (South Africa)", 6480154, "bnt-ngu", "Latn", } m["bnt-mpi"] = { "Mpiin", 93937013, "bnt-bdz", "Latn", } m["bnt-mpu"] = { "Mpuono", -- not to be confused with Mbuun zmp 36056, "bnt", "Latn", } m["bnt-ngu-pro"] = { "Proto-Nguni", 961559, "bnt-ngu", "Latn", type = "reconstructed", sort_key = {remove_diacritics = c.grave .. c.acute .. c.circ .. c.caron}, } m["bnt-phu"] = { "Phuthi", 33796, "bnt-ngu", "Latn", strip_diacritics = {remove_diacritics = c.grave .. c.acute}, } m["bnt-pro"] = { "Proto-Bantu", 3408025, "bnt", "Latn", type = "reconstructed", sort_key = "bnt-pro-sortkey", } m["bnt-sab-pro"] = { "Proto-Sabaki", nil, -- Q2209395 is the code for the Sabaki family "bnt-sab", "Latn", type = "reconstructed", } m["bnt-sbo"] = { "South Boma", nil, "bnt", "Latn", } m["bnt-sts-pro"] = { "Proto-Sotho-Tswana", 116773278, "bnt-sts", "Latn", type = "reconstructed", } m["btk-pro"] = { "Proto-Batak", 116773191, "btk", "Latn", type = "reconstructed", } m["cau-abz-pro"] = { "Proto-Abkhaz-Abaza", 7251831, "cau-abz", "Latn", type = "reconstructed", } m["cau-and-pro"] = { "Proto-Andian", nil, "cau-and", "Latn", type = "reconstructed", } m["cau-ava-pro"] = { "Proto-Avaro-Andian", 116773187, "cau-ava", "Latn", type = "reconstructed", } m["cau-cir-pro"] = { "Proto-Circassian", 7251838, "cau-cir", "Latn", type = "reconstructed", } m["cau-drg-pro"] = { "Proto-Dargwa", 116773205, "cau-drg", "Latn", type = "reconstructed", } m["cau-lzg-pro"] = { "Proto-Lezghian", 116773223, "cau-lzg", "Latn", type = "reconstructed", } m["cau-nec-pro"] = { "Proto-Northeast Caucasian", 116773244, "cau-nec", "Latn", type = "reconstructed", } m["cau-nkh-pro"] = { "Proto-Nakh", 108032840, "cau-nkh", "Latn", type = "reconstructed", } m["cau-nwc-pro"] = { "Proto-Northwest Caucasian", 7251861, "cau-nwc", "Latn", type = "reconstructed", } m["cau-tsz-pro"] = { "Proto-Tsezian", 116773287, "cau-tsz", "Latn", type = "reconstructed", } m["cba-ata"] = { "Atanques", 4812783, "cba", "Latn", } m["cba-cat"] = { "Catío Chibcha", 7083619, "cba", "Latn", } m["cba-dor"] = { "Dorasque", 5297532, "cba", "Latn", } m["cba-dui"] = { "Duit", 3041061, "cba", "Latn", } m["cba-hue"] = { "Huetar", 35514, "cba", "Latn", } m["cba-nut"] = { "Nutabe", 7070405, "cba", "Latn", } m["cba-pro"] = { "Proto-Chibchan", 116773203, "cba", "Latn", type = "reconstructed", } m["ccs-pro"] = { "Proto-Kartvelian", 2608203, "ccs", "Latn", type = "reconstructed", strip_diacritics = { from = {"q̣", "p̣", "ʓ", "ċ"}, to = {"q̇", "ṗ", "ʒ", "c̣"} }, } m["ccs-gzn-pro"] = { "Proto-Georgian-Zan", 23808119, "ccs-gzn", "Latn", type = "reconstructed", strip_diacritics = { from = {"q̣", "p̣", "ʓ", "ċ"}, to = {"q̇", "ṗ", "ʒ", "c̣"} }, } m["cdc-cbm-pro"] = { "Proto-Central Chadic", 116773197, "cdc-cbm", "Latn", type = "reconstructed", } m["cdc-mas-pro"] = { "Proto-Masa", 116773789, "cdc-mas", "Latn", type = "reconstructed", } m["cdc-pro"] = { "Proto-Chadic", 116773201, "cdc", "Latn", type = "reconstructed", } m["cdd-pro"] = { "Proto-Caddoan", 116773725, "cdd", "Latn", type = "reconstructed", } m["cel-bry-pro"] = { "Proto-Brythonic", 1248800, "cel-bry", "Latn, Polyt", sort_key = { Latn = "cel-bry-pro-sortkey", }, -- Polyt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["cel-gal"] = { "Gallaecian", 3094789, "cel-his", } m["cel-gau"] = { "Gaulish", 29977, "cel", "Latn, Polyt, Ital", strip_diacritics = { Latn = {remove_diacritics = c.macron .. c.breve .. c.diaer}, }, sort_key = { Latn = "cel-bry-pro-sortkey", }, -- Ital translit in [[Module:scripts/data]] -- Polyt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["cel-pro"] = { "Proto-Celtic", 653649, "cel", "Latn", type = "reconstructed", sort_key = "cel-pro-sortkey", } m["chi-pro"] = { "Proto-Chimakuan", 116773734, "chi", "Latn", type = "reconstructed", } m["chm-pro"] = { "Proto-Mari", 116773788, "chm", "Latn", type = "reconstructed", } m["cmc-pro"] = { "Proto-Chamic", 114793834, "cmc", "Latn", type = "reconstructed", } m["crp-bip"] = { "Basque-Icelandic Pidgin", 810378, "crp", "Latn", ancestors = "eu", } m["crp-cpr"] = { "Chinese Pidgin Russian", nil, "crp", "Hani, Cyrl, Latn", ancestors = "ru, zh", translit = {Cyrl = "ru-translit"}, strip_diacritics = { Cyrl = {remove_diacritics = c.acute .. c.grave .. c.macron}, }, } m["crp-gep"] = { "West Greenlandic Pidgin", 17036301, "crp", "Latn", ancestors = "kl", } m["crp-kia"] = { "Kiautschou German Pidgin", 108314615, "crp", "Latn", ancestors = "de", } m["crp-mar"] = { "Maroon Spirit Language", 1093206, "crp", "Latn", ancestors = "en", } m["crp-mpp"] = { "Macau Pidgin Portuguese", 128804537, "crp", "Hant, Latn", ancestors = "pt", sort_key = {Hant = "Hani-sortkey"}, } m["crp-rsn"] = { "Russenorsk", 505125, "crp", "Cyrl, Latn", ancestors = "nn, ru", translit = {Cyrl = "ru-translit"}, } m["crp-spp"] = { "Samoan Plantation Pidgin", 7409948, "crp", "Latn", ancestors = "en", } m["crp-slb"] = { "Solombala English", 7558525, "crp", "Cyrl, Latn", ancestors = "en, ru", translit = {Cyrl = "ru-translit"}, } m["crp-tpr"] = { "Taimyr Pidgin Russian", 16930506, "crp", "Cyrl", ancestors = "ru", translit = "ru-translit", } m["csu-bba-pro"] = { "Proto-Bongo-Bagirmi", 116773722, "csu-bba", "Latn", type = "reconstructed", } m["csu-maa-pro"] = { "Proto-Mangbetu", 116773786, "csu-maa", "Latn", type = "reconstructed", } m["csu-pro"] = { "Proto-Central Sudanic", 116773730, "csu", "Latn", type = "reconstructed", } m["csu-sar-pro"] = { "Proto-Sara", 116773809, "csu-sar", "Latn", type = "reconstructed", } m["cus-ash"] = { "Ashraaf", 4805855, "cus-som", "Latn", } m["cus-hec-pro"] = { "Proto-Highland East Cushitic", 116773761, "cus-hec", "Latn", type = "reconstructed", } m["cus-som-pro"] = { "Proto-Somaloid", nil, "cus-som", "Latn", type = "reconstructed", } m["cus-sou-pro"] = { "Proto-South Cushitic", 126081567, "cus-sou", "Latn", type = "reconstructed", } m["cus-pro"] = { "Proto-Cushitic", 116773204, "cus", "Latn", type = "reconstructed", } m["dmn-dam"] = { "Dama (Sierra Leone)", 19601574, "dmn", "Latn", } m["dra-bry"] = { "Beary", 1089116, "qfa-mix", "Mlym, Knda", ancestors = "ml, tcy", -- Knda translit in [[Module:scripts/data]] -- Mlym translit in [[Module:scripts/data]] } m["dra-cen-pro"] = { "Proto-Central Dravidian", nil, "dra-cen", "Latn", type = "reconstructed", } m["dra-mkn"] = { "Middle Kannada", 128810572, "dra-kan", "Knda", -- Knda translit in [[Module:scripts/data]] } m["dra-nor-pro"] = { "Proto-North Dravidian", 124433593, "dra-nor", "Latn", type = "reconstructed", } m["dra-okn"] = { "Old Kannada", 15723156, "dra-kan", "Knda", -- Knda translit in [[Module:scripts/data]] } m["dra-ote"] = { "Old Telugu", 126720868, "dra-tel", "Telu", translit = "te-translit", } m["dra-pro"] = { "Proto-Dravidian", 1702853, "dra", "Latn", type = "reconstructed", } m["dra-sdo-pro"] = { "Proto-South Dravidian I", 104847952, -- Wikipedia's "Proto-South Dravidian" is Proto-South Dravidian I in this scheme. "dra-sdo", "Latn", type = "reconstructed", } m["dra-sdt-pro"] = { "Proto-South Dravidian II", 128885257, "dra-sdt", "Latn", type = "reconstructed", } m["dra-sou-pro"] = { "Proto-South Dravidian", 128886121, "dra-sou", "Latn", type = "reconstructed", } m["egx-dem"] = { "Demotic Egyptian", 36765, "egx", "Latn, Egyd, Polyt", sort_key = { Latn = { remove_diacritics = "'%-%s", from = {"ꜣ", "j", "e", "ꜥ", "y", "w", "b", "p", "f", "m", "n", "r", "l", "ḥ", "ḫ", "h̭", "ẖ", "h", "š", "s", "q", "k", "g", "ṱ", "ṯ", "t", "ḏ", "%.", "⸗"}, to = {p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[15], p[16], p[16], p[17], p[14], p[19], p[18], p[20], p[21], p[22], p[23], p[24], p[23], p[25], p[26], p[26]} }, }, -- Polyt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["dmn-pro"] = { "Proto-Mande", 116773785, "dmn", "Latn", type = "reconstructed", } m["dmn-mdw-pro"] = { "Proto-Western Mande", 116773822, "dmn-mdw", "Latn", type = "reconstructed", } m["dru-pro"] = { "Proto-Rukai", 116773807, "map", "Latn", type = "reconstructed", } m["ero-gsz"] = { "Geshiza", nil, "ero", "Latn", } m["ero-nya"] = { "Nyagrong Minyag", nil, "ero", "Latn", } m["ero-tau"] = { "Stau", nil, "ero", "Latn", } m["esx-esk-pro"] = { "Proto-Eskimo", 7251842, "esx-esk", "Latn", type = "reconstructed", display_text = s["esx-displaytext"], strip_diacritics = s["esx-stripdiacritics"], } m["esx-ink"] = { "Inuktun", 1671647, "esx-inu", "Latn", } m["esx-inq"] = { "Inuinnaqtun", 28070, "esx-inu", "Latn", } m["esx-inu-pro"] = { "Proto-Inuit", 60785588, "esx-inu", "Latn", type = "reconstructed", display_text = s["esx-displaytext"], strip_diacritics = s["esx-stripdiacritics"], } m["esx-pro"] = { "Proto-Eskimo-Aleut", 7251843, "esx", "Latn", type = "reconstructed", display_text = s["esx-displaytext"], strip_diacritics = s["esx-stripdiacritics"], } m["esx-tut"] = { "Tunumiisut", 15665389, "esx-inu", "Latn", } m["euq-pro"] = { "Proto-Basque", 938011, "euq", "Latn", type = "reconstructed", } m["gba-pro"] = { "Proto-Gbaya", nil, "gba", "Latn", type = "reconstructed", } m["gem-pro"] = { "Proto-Germanic", 669623, "gem", "Latn", type = "reconstructed", sort_key = "gem-pro-sortkey", } m["gme-bur"] = { "Burgundian", 47625, "gme", "Latn", } m["gme-cgo"] = { "Crimean Gothic", 36211, "gme", "Latn", } m["gmq-gut"] = { "Gutnish", 1256646, "gmq", "Latn", ancestors = "gmq-ogt", } m["gmq-jmk"] = { "Jamtish", 35512, "gmq-eas", "Latn", } m["gmq-mno"] = { "Middle Norwegian", 3417070, "gmq-wes", "Latn", } m["gmq-oda"] = { "Old Danish", 12330003, "gmq-eas", "Latn, Runr", strip_diacritics = {remove_diacritics = c.macron}, } m["gmq-ogt"] = { "Old Gutnish", 1133488, "gmq", "Latn, Runr", ancestors = "non", } m["gmq-osw"] = { "Old Swedish", 2417210, "gmq-eas", "Latn, Runr", strip_diacritics = {remove_diacritics = c.macron}, } m["gmq-pro"] = { "Proto-Norse", 1671294, "gmq", "Runr", translit = "Runr-translit", } m["gmq-scy"] = { "Scanian", 768017, "gmq-eas", "Latn", } m["gmw-bgh"] = { "Bergish", 329030, "gmw-frk", "Latn", } m["gmw-cfr"] = { "Central Franconian", 572197, "gmw-hgm", "Latn", ancestors = "gmh", wikimedia_codes = "ksh", } m["gmw-ecg"] = { "East Central German", 499344, -- subsumes Q699284, Q152965 "gmw-hgm", "Latn", ancestors = "gmh", } m["gmw-fin"] = { "Fingallian", 3072588, "gmw-ian", "Latn", } m["gmw-gts"] = { "Gottscheerish", 533109, "gmw-hgm", "Latn", ancestors = "bar", } m["gmw-jdt"] = { "Jersey Dutch", 1687911, "gmw-frk", "Latn", ancestors = "nl", } m["gmw-msc"] = { "Middle Scots", 3327000, "gmw-ang", "Latn", ancestors = "enm-esc", } m["gmw-pro"] = { "Proto-West Germanic", 78079021, "gmw", "Latn, Runr", -- type = "reconstructed", -- largely but not entirely reconstructed (like Proto-Norse); see April '24 BP, set back to reconstructed (?) if 'anti-asterisk' is added sort_key = "gmw-pro-sortkey", } m["gmw-rfr"] = { "Rhine Franconian", 707007, "gmw-hgm", "Latn", ancestors = "gmh", } m["gmw-stm"] = { "Sathmar Swabian", 2223059, "gmw-hgm", "Latn", ancestors = "swg", } m["gmw-tsx"] = { "Transylvanian Saxon", 260942, "gmw-hgm", "Latn", ancestors = "gmw-cfr", } m["gmw-vog"] = { "Volga German", 312574, "gmw-hgm", "Latn", ancestors = "gmw-rfr", } m["gmw-zps"] = { "Zipser German", 205548, "gmw-hgm", "Latn", ancestors = "gmh", } m["gn-cls"] = { "Classical Guarani", 17478065, "gn", "Latn", } m["grk-cal"] = { "Calabrian Greek", 1146398, "grk", "Latn, Grek", ancestors = "grk-ita", translit = { Grek = "el-translit", }, -- Grek display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["grk-car"] = { "Cargesian Greek", 16549443, "grk", "Latn, Grek", ancestors = "gkm", } m["grk-ita"] = { "Italiot Greek", 19720507, "grk", "Latn, Grek", ancestors = "gkm", translit = { Grek = "el-translit", }, -- Grek display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["grk-mar"] = { "Mariupol Greek", 4400023, "grk", "Cyrl, Latn, Grek", ancestors = "gkm", translit = { Cyrl = "grk-mar-translit", Grek = "grk-mar-translit", }, override_translit = true, strip_diacritics = { Cyrl = {remove_diacritics = c.acute}, }, -- Grek display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["grk-pro"] = { "Proto-Hellenic", 1231805, "grk", "Latn, Polyt", type = "reconstructed", sort_key = {Latn = { from = {"ʰ", "ʷ"}, to = {"h", "w"}, remove_diacritics = c.grave .. c.acute .. c.macron .. c.breve .. c.caron .. c.CGJ }}, display_text = {Latn = { from = {"([dlLt])" .. c.caron}, to = {"%1" .. c.CGJ .. c.caron}, }}, strip_diacritics = {Latn = { from = {"([dlLt])" .. c.caron}, to = {"%1" .. c.CGJ .. c.caron}, }}, -- Polyt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]] -- NOTE: formerly no translit specified for Polyt; presumably an accidental omission; if not, set Polyt = false in -- the translit section } m["hmn-pro"] = { "Proto-Hmongic", 116773210, "hmn", "Latn", type = "reconstructed", } m["hmx-mie-pro"] = { "Proto-Mienic", 116773229, "hmx-mie", "Latn", type = "reconstructed", } m["hmx-pro"] = { "Proto-Hmong-Mien", 7251846, "hmx", "Latn", type = "reconstructed", } m["hyx-pro"] = { "Proto-Armenian", 3848498, "hyx", "Latn", type = "reconstructed", } m["iir-nur-pro"] = { "Proto-Nuristani", 116773248, "iir-nur", "Latn", type = "reconstructed", } m["iir-pro"] = { "Proto-Indo-Iranian", 966439, "iir", "Latn", type = "reconstructed", } m["ijo-pro"] = { "Proto-Ijoid", 116773766, "ijo", "Latn", type = "reconstructed", } m["inc-apa"] = { "Apabhramsa", 616419, "inc-mid", "Deva, Shrd, Sidd", ancestors = "pra", translit = { Deva = "sa-translit", -- Shrd translit in [[Module:scripts/data]] -- Sidd translit in [[Module:scripts/data]] }, } m["inc-ash"] = { "Ashokan Prakrit", 104854379, "inc-mid", "Brah, Khar", ancestors = "sa", translit = { -- Brah translit in [[Module:scripts/data]] Khar = "Khar-translit", }, } m["inc-dng-pro"] = { "Proto-Dangari", nil, "inc-dng", "Latn", type = "reconstructed", } m["inc-kam"] = { "Kamarupi Prakrit", 6356097, "inc-bas", "Brah, Sidd", -- Brah, Sidd translit in [[Module:scripts/data]] } m["inc-kho"] = { "Kholosi", 24952008, "inc-snd", "Latn, Arab", } m["inc-khr"] = { "Khortha", 13406670, "inc-sad", "Deva, Kthi", translit = { Deva = "bho-translit", Kthi = "bho-Kthi-translit", }, } m["inc-krd-pro"] = { "Proto-Kamta", 128816843, "inc-bas", "Latn", ancestors = "inc-kam", type = "reconstructed", } m["inc-mas"] = { "Middle Assamese", 128806836, "inc-bas", "as-Beng", ancestors = "inc-oas", translit = "inc-mas-translit", } m["inc-mbn"] = { "Middle Bengali", 113559927, "inc-bas", "Beng", ancestors = "inc-obn", translit = "inc-mbn-translit", } m["inc-mgu"] = { "Middle Gujarati", 24907429, "inc-wes", "Deva", ancestors = "inc-ogu", } m["inc-mor"] = { "Middle Odia", 128810882, "inc-eas", "Orya", ancestors = "inc-oor", } m["inc-oas"] = { "Early Assamese", 85758237, "inc-bas", "as-Beng", ancestors = "inc-kam", translit = "inc-oas-translit", } m["inc-oaw"] = { "Old Awadhi", nil, "inc-hie", "Deva, Kthi, Aran", strip_diacritics = { from = {"هٔ", "ۂ"}, -- character "ۂ" code U+06C2 to "ه" and "هٔ" (U+0647 + U+0654) to "ه" to = {"ہ", "ہ"}, remove_diacritics = c.fathatan .. c.dammatan .. c.kasratan .. c.fatha .. c.damma .. c.kasra .. c.shadda .. c.sukun .. c.nunghunna .. c.superalef }, translit = { Deva = "sa-translit", Kthi = "sa-Kthi-translit", Aran = "inc-ohi-translit", }, } m["inc-obn"] = { "Old Bengali", 113559926, "inc-bas", "Beng", } m["inc-ogu"] = { "Old Gujarati", 24907427, "inc-wes", "Deva", translit = "sa-translit", } m["inc-ohi"] = { "Old Hindi", 48767781, "inc-hiw", "Deva, Aran", strip_diacritics = { from = {"هٔ", "ۂ"}, -- character "ۂ" code U+06C2 to "ه" and "هٔ" (U+0647 + U+0654) to "ه" to = {"ہ", "ہ"}, remove_diacritics = c.fathatan .. c.dammatan .. c.kasratan .. c.fatha .. c.damma .. c.kasra .. c.shadda .. c.sukun .. c.nunghunna .. c.superalef }, translit = { Deva = "sa-translit", Aran = "inc-ohi-translit", }, } m["inc-oor"] = { "Old Odia", 128807801, "inc-eas", "Orya", } m["inc-opa"] = { "Old Punjabi", 115270971, "inc-pan", "Guru, Aran", translit = { Guru = "inc-opa-Guru-translit", Aran = "pa-Aran-translit", }, strip_diacritics = {remove_diacritics = c.fathatan .. c.dammatan .. c.kasratan .. c.fatha .. c.damma .. c.kasra .. c.shadda .. c.sukun}, } m["inc-pro"] = { "Proto-Indo-Aryan", 23808344, "inc", "Latn", type = "reconstructed", } m["inc-sar"] = { "Sarazi", 85799728, "him", "Aran, Deva, Takr", strip_diacritics = { from = {"هٔ", "ۂ"}, -- character "ۂ" code U+06C2 to "ه" and "هٔ" (U+0647 + U+0654) to "ه" to = {"ہ", "ہ"}, remove_diacritics = c.fathatan .. c.dammatan .. c.kasratan .. c.fatha .. c.damma .. c.kasra .. c.shadda .. c.sukun .. c.nunghunna .. c.superalef }, translit = { Aran = "ur-translit", Deva = "hi-translit", -- Takr = "Takr-translit", }, } m["ine-ana-pro"] = { "Proto-Anatolian", 7251833, "ine-ana", "Latn", type = "reconstructed", } m["ine-bsl-pro"] = { "Proto-Balto-Slavic", 1703347, "ine-bsl", "Latn", type = "reconstructed", sort_key = { from = {"[áā]", "[éēḗ]", "[íī]", "[óōṓ]", "[úū]", c.acute, c.macron, "ˀ"}, to = {"a", "e", "i", "o", "u"} }, } m["ine-kal"] = { "Kalašma", 122770439, "ine-ana", "Xsux", } m["ine-pae"] = { "Paeonian", 2705672, "ine", "Polyt", -- Polyt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["ine-pro"] = { "Proto-Indo-European", 37178, "ine", "Latn", type = "reconstructed", sort_key = { from = {"[áā]", "[éēḗ]", "[íī]", "[óōṓ]", "[úū]", "ĺ", "ḿ", "ń", "ŕ", "ǵ", "ḱ", "ʰ", "ʷ", "₁", "₂", "₃", c.ringbelow, c.acute, c.macron}, to = {"a", "e", "i", "o", "u", "l", "m", "n", "r", "g'", "k'", "¯h", "¯w", "1", "2", "3"} }, } m["ine-toc-pro"] = { "Proto-Tocharian", 104841462, "ine-toc", "Latn", type = "reconstructed", } m["xme-old"] = { "Old Median", 36461, "xme", "Polyt, Latn", -- Polyt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["xme-mid"] = { "Middle Median", 12836150, "xme", "Latn", } m["xme-ker"] = { "Kermanic", 129850, "xme", "Arab, Latn, Hebr", ancestors = "xme-mid", -- Hebr display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["xme-taf"] = { "Tafreshi", nil, "xme", "Arab, Latn", ancestors = "xme-mid", } m["xme-ttc-pro"] = { "Proto-Tatic", 122973870, "xme-ttc", "Latn", ancestors = "xme-mid", } m["xme-kls"] = { "Kalasuri", nil, "xme-ttc", ancestors = "xme-ttc-nor", } m["xme-klt"] = { "Kilit", 3612452, "xme-ttc", "Cyrl", -- and Arab? } m["xme-ott"] = { "Old Tati", 434697, "xme-ttc", "Arab, Latn", } m["ira-kms-pro"] = { "Proto-Komisenian", 116773777, "ira-kms", "Latn", type = "reconstructed", } m["ira-mpr-pro"] = { "Proto-Medo-Parthian", 116773227, "ira-mpr", "Latn", type = "reconstructed", } m["ira-pat-pro"] = { "Proto-Pathan", 116773255, "ira-pat", "Latn", type = "reconstructed", } m["ira-pro"] = { "Proto-Iranian", 4167865, "ira", "Latn", type = "reconstructed", } m["ira-zgr-pro"] = { "Proto-Zaza-Gorani", 116775031, "ira-zgr", "Latn", type = "reconstructed", } m["xsc-pro"] = { "Proto-Scythian", 116773273, "xsc", "Latn", type = "reconstructed", } m["xsc-sar-pro"] = { "Proto-Sarmatian", 116773249, "xsc-sar", "Latn", type = "reconstructed", } m["xsc-skw-pro"] = { "Proto-Saka-Wakhi", 116773267, "xsc-skw", "Latn", type = "reconstructed", } m["xsc-sak-pro"] = { "Proto-Saka", 116773264, "xsc-sak", "Latn", type = "reconstructed", } m["ira-sym-pro"] = { "Proto-Shughni-Yazghulami-Munji", 116773813, "ira-sym", "Latn", type = "reconstructed", } m["ira-sgi-pro"] = { "Proto-Sanglechi-Ishkashimi", 116773808, "ira-sgi", "Latn", type = "reconstructed", } m["ira-mny-pro"] = { "Proto-Munji-Yidgha", 116773792, "ira-mny", "Latn", type = "reconstructed", } m["ira-shy-pro"] = { "Proto-Shughni-Yazghulami", 116773812, "ira-shy", "Latn", type = "reconstructed", } m["ira-shr-pro"] = { "Proto-Shughni-Roshani", 116773811, "ira-shr", "Latn", type = "reconstructed", } m["ira-sgc-pro"] = { "Proto-Sogdic", 116773276, "ira-sgc", "Latn", type = "reconstructed", } m["ira-wnj"] = { "Vanji", 3398419, "ira-shy", "Latn", } m["iro-ere"] = { "Erie", 5388365, "iro-nor", "Latn", } m["iro-min"] = { "Mingo", 128531, "iro-nor", "Latn", ietf_subtag = "i-mingo", -- grandfathered IETF tag } m["iro-nor-pro"] = { "Proto-North Iroquoian", 116773242, "iro-nor", "Latn", type = "reconstructed", } m["iro-pro"] = { "Proto-Iroquoian", 7251852, "iro", "Latn", type = "reconstructed", } m["itc-pro"] = { "Proto-Italic", 17102720, "itc", "Latn", type = "reconstructed", } m["itc-psa"] = { "Pre-Samnite", 7239186, "itc-sbl", "Ital, Polyt, Latn", -- Ital translit in [[Module:scripts/data]] (NOTE: formerly not present, probably an accidental omission) -- Polyt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["jpx-hcj"] = { "Hachijō", 5637049, "jpx", "Jpan", ancestors = "ojp-eas", translit = s["jpx-translit"], display_text = s["jpx-displaytext"], strip_diacritics = s["jpx-stripdiacritics"], sort_key = s["jpx-sortkey"], } m["jpx-pro"] = { "Proto-Japonic", 3924309, "jpx", "Latn", type = "reconstructed", } m["jpx-ryu-pro"] = { "Proto-Ryukyuan", 56349069, "jpx-ryu", "Latn", type = "reconstructed", } m["kar-pro"] = { "Proto-Karen", 85794783, "kar", "Latn", type = "reconstructed", } m["kca-eas"] = { "Eastern Khanty", 30304622, "kca", "Cyrl", translit = "kca-translit", override_translit = true, -- TODO temporary until MediaWiki supports Unicode 16 (probably requires a PHP update from their side) sort_key = { Cyrl = { from = {"ᲊ"}, to = {"Ᲊ"} } }, } m["kca-nor"] = { "Northern Khanty", 30304527, "kca", "Cyrl", translit = "kca-translit", override_translit = true, -- TODO temporary until MediaWiki supports Unicode 16 (probably requires a PHP update from their side) sort_key = { Cyrl = { from = {"ᲊ"}, to = {"Ᲊ"} } }, } m["kca-pro"] = { "Proto-Khanty", 127505171, "kca", "Latn", type = "reconstructed", } m["kca-sou"] = { "Southern Khanty", 30304618, "kca", "Cyrl", translit = "kca-translit", override_translit = true, } m["khi-kho-pro"] = { "Proto-Khoe", 116773218, "khi-kho", "Latn", type = "reconstructed", } m["khi-kun"] = { "ǃKung", 32904, "khi-kxa", "Latn", } m["ko-ear"] = { "Early Modern Korean", 756014, "qfa-kor", "Kore", ancestors = "okm", translit = "okm-translit", -- Kore strip_diacritics in [[Module:scripts/data]] } m["kro-pro"] = { "Proto-Kru", 116773778, "kro", "Latn", type = "reconstructed", } m["ku-pro"] = { "Proto-Kurdish", 116773221, "ku", "Latn", type = "reconstructed", } m["map-ata-pro"] = { "Proto-Atayalic", 116773151, "map-ata", "Latn", type = "reconstructed", } m["map-bms"] = { "Banyumasan", 33219, "map", "Latn, Java", } m["map-pro"] = { "Proto-Austronesian", 49230, "map", "Latn", type = "reconstructed", } m["mis-hkl"] = { "Kelantan Peranakan Hokkien", 108794818, "qfa-mix", ancestors = "nan-hbl, sou, mfa", } m["mis-idn"] = { "Idiom Neutral", 35847, "art", "Latn", type = "appendix-constructed", } m["mis-isa"] = { "Isaurian", 16956868, nil, -- "Xsux, Hluw, Latn", } m["mis-jie"] = { "Jie", 124424186, nil, "Hani", sort_key = "Hani-sortkey", } m["mis-jzh"] = { "Jizhao", 45242758, "qfa-bej", "Latn", } m["mis-kas"] = { "Kassite", 35612, nil, "Xsux", } m["mis-mmd"] = { "Mimi of Decorse", 6862206, nil, "Latn", } m["mis-mmn"] = { "Mimi of Nachtigal", 6862207, nil, "Latn", } m["mis-phi"] = { "Philistine", 2230924, nil, "Phnx", -- Phnx translit in [[Module:scripts/data]] (NOTE: not present before, presumably an accidental omission) } m["mis-rou"] = { "Rouran", 48816637, "qfa-xgx", "Hani, Latn", sort_key = {Hani = "Hani-sortkey"}, } m["mis-tdl"] = { "Turdulian", 133176492, } m["mis-tdt"] = { "Turdetanian", 133176461, } m["mis-tnw"] = { "Tangwang", 7683179, "qfa-mix", "Latn", ancestors = "cmn, sce", } m["mis-tuh"] = { "Tuyuhun", 48816625, "qfa-xgx", "Hani, Latn", sort_key = {Hani = "Hani-sortkey"}, } m["mis-tuo"] = { "Tuoba", 48816629, "qfa-xgx", "Hani, Latn", sort_key = {Hani = "Hani-sortkey"}, } m["mis-wuh"] = { "Wuhuan", 118976867, "qfa-xgx", "Hani, Latn", sort_key = {Hani = "Hani-sortkey"}, } m["mis-xbi"] = { "Xianbei", 4448647, "qfa-xgx", "Hani, Latn", sort_key = {Hani = "Hani-sortkey"}, } m["mis-xnu"] = { "Xiongnu", 10901674, nil, "Hani, Latn", sort_key = {Hani = "Hani-sortkey"}, } m["mjg-mgl"] = { "Mongghul", 53765528, "mjg", "Latn", -- also Mong, Cyrl ? } m["mjg-mgr"] = { "Mangghuer", 56285392, "mjg", "Latn", -- also Mong, Cyrl ? } m["mkh-asl-pro"] = { "Proto-Aslian", 55630680, "mkh-asl", "Latn", type = "reconstructed", } m["mkh-ban-pro"] = { "Proto-Bahnaric", 116773189, "mkh-ban", "Latn", type = "reconstructed", } m["mkh-kat-pro"] = { "Proto-Katuic", 116773772, "mkh-kat", "Latn", type = "reconstructed", } m["mkh-khm-pro"] = { "Proto-Khmuic", 116773774, "mkh-khm", "Latn", type = "reconstructed", } m["mkh-kmr-pro"] = { "Proto-Khmeric", 55630684, "mkh-kmr", "Latn", type = "reconstructed", } m["mkh-mmn"] = { "Middle Mon", 121337926, "mkh-mnc", "Latn, Mymr", --and also Pallava ancestors = "omx", } m["mkh-mnc-pro"] = { "Proto-Monic", 116773231, "mkh-mnc", "Latn", type = "reconstructed", } m["mkh-mvi"] = { "Middle Vietnamese", 9199, "mkh-vie", "Hani, Latn", sort_key = {Hani = "Hani-sortkey"}, } m["mkh-pal-pro"] = { "Proto-Palaungic", 104847372, "mkh-pal", "Latn", type = "reconstructed", } m["mkh-pea-pro"] = { "Proto-Pearic", 116773804, "mkh-pea", "Latn", type = "reconstructed", } m["mkh-pkn-pro"] = { "Proto-Pakanic", 116773803, "mkh-pkn", "Latn", type = "reconstructed", } m["mkh-pro"] = { --This will be merged into 2015 aav-pro. "Proto-Mon-Khmer", 7251859, "mkh", "Latn", type = "reconstructed", } m["mnw-tha"] = { -- To be removed. "Thai Mon", nil, "mkh-mnc", "Mymr, Thai", ancestors = "mkh-mmn", sort_key = { from = {"[%p]", "ျ", "ြ", "ွ", "ှ", "ၞ", "ၟ", "ၠ", "ၚ", "ဿ", "[็-๎]", "([เแโใไ])([ก-ฮ])ฺ?"}, to = {"", "္ယ", "္ရ", "္ဝ", "္ဟ", "္န", "္မ", "္လ", "င", "သ္သ", "", "%2%1"} }, } m["mkh-vie-pro"] = { "Proto-Vietic", 109432616, "mkh-vie", "Latn", type = "reconstructed", } m["mns-cen"] = { "Central Mansi", 128810384, "mns", "Cyrl", translit = "mns-translit", override_translit = true, } m["mns-nor"] = { "Northern Mansi", 30304537, "mns", "Cyrl", translit = "mns-translit", override_translit = true, } m["mns-pro"] = { "Proto-Mansi", 128883093, "mns", "Latn", type = "reconstructed", } m["mns-sou"] = { "Southern Mansi", 30304629, "mns", "Cyrl", translit = "mns-translit", override_translit = true, } m["mun-pro"] = { "Proto-Munda", 105102373, "mun", "Latn", type = "reconstructed", } m["myn-chl"] = { -- the stage after ''emy'' "Ch'olti'", 873995, "myn", "Latn", } m["myn-pro"] = { "Proto-Mayan", 3321532, "myn", "Latn", type = "reconstructed", } m["nai-ala"] = { "Alazapa", 128810233, nil, "Latn", } m["nai-bay"] = { "Bayogoula", 1563704, nil, "Latn", } m["nai-cal"] = { "Calusa", 51782, nil, "Latn", } m["nai-chi"] = { "Chiquimulilla", 25339627, "nai-xin", "Latn", } m["nai-chu-pro"] = { "Proto-Chumash", 116773736, "nai-chu", "Latn", type = "reconstructed", } m["nai-cig"] = { "Ciguayo", 20741700, nil, "Latn", } m["nai-ckn-pro"] = { "Proto-Chinookan", 116773735, "nai-ckn", "Latn", type = "reconstructed", } m["nai-guz"] = { "Guazacapán", 19572028, "nai-xin", "Latn", } m["nai-hit"] = { "Hitchiti", 1542882, "nai-mus", "Latn", } m["nai-ipa"] = { "Ipai", 3027474, "nai-yuc", "Latn", } m["nai-jtp"] = { "Jutiapa", nil, "nai-xin", "Latn", } m["nai-jum"] = { "Jumaytepeque", 25339626, "nai-xin", "Latn", } m["nai-kat"] = { "Kathlamet", 6376639, "nai-ckn", "Latn", } m["nai-klp-pro"] = { "Proto-Kalapuyan", 116773771, "nai-klp", "Latn", type = "reconstructed", } m["nai-knm"] = { "Konomihu", 3198734, "nai-shs", "Latn", } m["nai-kum"] = { "Kumeyaay", 4910139, "nai-yuc", "Latn", } m["nai-mac"] = { "Macoris", 21070851, nil, "Latn", } m["nai-mdu-pro"] = { "Proto-Maidun", 116773784, "nai-mdu", "Latn", type = "reconstructed", } m["nai-miz-pro"] = { "Proto-Mixe-Zoquean", 7251858, "nai-miz", "Latn", type = "reconstructed", } m["nai-mus-pro"] = { "Proto-Muskogean", 116775368, "nai-mus", "Latn", type = "reconstructed", } m["nai-nao"] = { "Naolan", 6964594, nil, "Latn", } m["nai-nrs"] = { "New River Shasta", 7011254, "nai-shs", "Latn", } m["nai-okw"] = { "Okwanuchu", 3350126, "nai-shs", "Latn", } m["nai-per"] = { "Pericú", 3375369, nil, "Latn", } m["nai-pic"] = { "Picuris", 7191257, "nai-kta", "Latn", } m["nai-plp-pro"] = { "Proto-Plateau Penutian", 116773806, "nai-plp", "Latn", type = "reconstructed", } m["nai-pom-pro"] = { "Proto-Pomo", 116773262, "nai-pom", "Latn", type = "reconstructed", } m["nai-qng"] = { "Quinigua", 36360, nil, "Latn", } m["nai-sca-pro"] = { -- NB 'sio-pro' "Proto-Siouan" which is Proto-Western Siouan "Proto-Siouan-Catawban", 116773275, "nai-sca", "Latn", type = "reconstructed", } m["nai-sin"] = { "Sinacantán", 24190249, "nai-xin", "Latn", } m["nai-sln"] = { "Salvadoran Lenca", 3229434, "nai-len", "Latn", } m["nai-spt"] = { "Sahaptin", 3833015, "nai-shp", "Latn", } m["nai-tap"] = { "Tapachultec", 7684401, "nai-miz", "Latn", } m["nai-taw"] = { "Tawasa", 7689233, nil, "Latn", } m["nai-teq"] = { "Tequistlatec", 2964454, "nai-tqn", "Latn", } m["nai-tip"] = { "Tipai", 3027471, "nai-yuc", "Latn", } m["nai-tot-pro"] = { "Proto-Totozoquean", 116773285, "nai-tot", "Latn", type = "reconstructed", } m["nai-tsi-pro"] = { "Proto-Tsimshianic", nil, "nai-tsi", "Latn", type = "reconstructed", } m["nai-utn-pro"] = { "Proto-Utian", 116773290, "nai-utn", "Latn", type = "reconstructed", } m["nai-wai"] = { "Waikuri", 3118702, nil, "Latn", } m["nai-wji"] = { "Western Jicaque", 3178610, "nai-jcq", "Latn", } m["nai-yup"] = { "Yupiltepeque", 25339628, "nai-xin", "Latn", } m["nan-dat"] = { "Datian Min", 19855572, "zhx-nan", "Hants", generate_alternants = "zh-generatealternants", sort_key = "Hani-sortkey", } m["nan-hbl"] = { "Hokkien", 1624231, "zhx-nan", "Hants, Latn, Bopo, Kana", wikimedia_codes = "zh-min-nan", generate_alternants = "zh-generatealternants", sort_key = { Hani = "Hani-sortkey", Kana = "Kana-sortkey" }, } m["nan-hlh"] = { "Hailufeng Min", 120755728, "zhx-nan", "Hants", generate_alternants = "zh-generatealternants", sort_key = "Hani-sortkey", } m["nan-lnx"] = { "Longyan Min", 6674568, "zhx-nan", "Hants", generate_alternants = "zh-generatealternants", sort_key = "Hani-sortkey", } m["nan-tws"] = { "Teochew", 36759, "zhx-nan", "Hants", generate_alternants = "zh-generatealternants", translit = "zh-translit", sort_key = "Hani-sortkey", } m["nan-zhe"] = { "Zhenan Min", 3846710, "zhx-nan", "Hants", generate_alternants = "zh-generatealternants", sort_key = "Hani-sortkey", } m["nan-zsh"] = { "Sanxiang Min", 7420769, "zhx-nan", "Hants", generate_alternants = "zh-generatealternants", sort_key = "Hani-sortkey", } m["ngf-bin-pro"] = { "Proto-Binanderean", 137881672, "ngf-bin", "Latn", type = "reconstructed", } m["ngf-pro"] = { "Proto-Trans-New Guinea", 85794785, "ngf", "Latn", type = "reconstructed", } m["nic-bco-pro"] = { "Proto-Benue-Congo", 116773194, "nic-bco", "Latn", type = "reconstructed", } m["nic-bod-pro"] = { "Proto-Bantoid", 116773190, "nic-bod", "Latn", type = "reconstructed", } m["nic-eov-pro"] = { "Proto-Eastern Oti-Volta", 116773753, "nic-eov", "Latn", type = "reconstructed", } m["nic-gns-pro"] = { "Proto-Gurunsi", 116773759, "nic-gns", "Latn", type = "reconstructed", } m["nic-grf-pro"] = { "Proto-Grassfields", 116773755, "nic-grf", "Latn", type = "reconstructed", } m["nic-gur-pro"] = { "Proto-Gur", 116773758, "nic-gur", "Latn", type = "reconstructed", } m["nic-jkn-pro"] = { "Proto-Jukunoid", 116773769, "nic-jkn", "Latn", type = "reconstructed", } m["nic-lcr-pro"] = { "Proto-Lower Cross River", 116773782, "nic-lcr", "Latn", type = "reconstructed", } m["nic-ogo-pro"] = { "Proto-Ogoni", 116773799, "nic-ogo", "Latn", type = "reconstructed", } m["nic-ovo-pro"] = { "Proto-Oti-Volta", 116773802, "nic-ovo", "Latn", type = "reconstructed", } m["nic-plt-pro"] = { "Proto-Plateau", 116773805, "nic-plt", "Latn", type = "reconstructed", } m["nic-pro"] = { "Proto-Niger-Congo", 108000748, "nic", "Latn", type = "reconstructed", } m["nic-ubg-pro"] = { "Proto-Ubangian", 116773818, "nic-ubg", "Latn", type = "reconstructed", } m["nic-ucr-pro"] = { "Proto-Upper Cross River", 116773819, "nic-ucr", "Latn", type = "reconstructed", } m["nic-vco-pro"] = { "Proto-Volta-Congo", 116773293, "nic-vco", "Latn", type = "reconstructed", } m["njo-jgl"] = { "Chungli Ao", 55607615, "njo", "Latn", } m["njo-mng"] = { "Mongsen Ao", 85383221, "njo", "Latn", } m["nub-har"] = { "Haraza", 19572059, "nub", "Arab, Latn", } m["nub-pro"] = { "Proto-Nubian", 116773246, "nub", "Latn", type = "reconstructed", } m["omq-cha-pro"] = { "Proto-Chatino", 116773202, "omq-cha", "Latn", type = "reconstructed", } m["omq-maz-pro"] = { "Proto-Mazatec", 116773790, "omq-maz", "Latn", type = "reconstructed", } m["omq-mix-pro"] = { "Proto-Mixtecan", 21573423, "omq-mix", "Latn", type = "reconstructed", } m["omq-mxt-pro"] = { "Proto-Mixtec", 21573424, "omq-mxt", "Latn", type = "reconstructed", } m["omq-otp-pro"] = { "Proto-Oto-Pamean", 116773251, "omq-otp", "Latn", type = "reconstructed", } m["omq-pro"] = { "Proto-Oto-Manguean", 33669, "omq", "Latn", type = "reconstructed", } m["omq-sjq"] = { "San Juan Quiahije Chatino", 138330751, "omq-cha", "Latn", } m["omq-tel"] = { "Teposcolula Mixtec", nil, "omq-mxt", "Latn", } m["omq-teo"] = { "Teojomulco Chatino", 25340451, "omq-cha", "Latn", } m["omq-tri-pro"] = { "Proto-Triqui", 116773817, "omq-tri", "Latn", type = "reconstructed", } m["omq-zap-pro"] = { "Proto-Zapotecan", 116773297, "omq-zap", "Latn", type = "reconstructed", } m["omq-zpc-pro"] = { "Proto-Zapotec", 116773296, "omq-zpc", "Latn", type = "reconstructed", } m["omv-aro-pro"] = { "Proto-Aroid", 116773721, "omv-aro", "Latn", type = "reconstructed", } m["omv-diz-pro"] = { "Proto-Dizoid", 116773750, "omv-diz", "Latn", type = "reconstructed", } m["omv-pro"] = { "Proto-Omotic", 116773800, "omv", "Latn", type = "reconstructed", } m["oto-otm-pro"] = { "Proto-Otomi", 5908710, "oto-otm", "Latn", type = "reconstructed", } m["oto-pro"] = { "Proto-Otomian", 116773252, "oto", "Latn", type = "reconstructed", } m["paa-kmn"] = { "Kómnzo", 18344310, "paa-wko", "Latn", } m["paa-kwn"] = { "Kuwani", 6449056, "qfa-unc", -- poorly attested, possibly the same as or related to Kalabra "Latn", } m["paa-lei"] = { "Leitre", 85776228, "paa-isk", } m["paa-nha-pro"] = { "Proto-North Halmahera", 116773241, "paa-nha", "Latn", type = "reconstructed" } m["paa-nun"] = { "Nungon", 128807788, "ngf-ynu", "Latn", } m["phi-din"] = { "Dinapigue Agta", 16945774, "phi", "Latn", } m["phi-kal-pro"] = { "Proto-Kalamian", 116773213, "phi-kal", "Latn", type = "reconstructed", } m["phi-nag"] = { "Nagtipunan Agta", 16966111, "phi", "Latn", } m["phi-pro"] = { "Proto-Philippine", 18204898, "phi", "Latn", type = "reconstructed", } m["poz-abi"] = { "Abai", 19570729, "poz-san", "Latn", } m["poz-bal"] = { "Baliledo", 4850912, "poz", "Latn", } m["poz-btk-pro"] = { "Proto-Bungku-Tolaki", 116773724, "poz-btk", "Latn", type = "reconstructed", } m["poz-cet-pro"] = { "Proto-Central-Eastern Malayo-Polynesian", 2269883, "poz-cet", "Latn", type = "reconstructed", } m["poz-hce-pro"] = { "Proto-Halmahera-Cenderawasih", 116773209, "poz-hce", "Latn", type = "reconstructed", } m["poz-lgx-pro"] = { "Proto-Lampungic", 116773222, "poz-lgx", "Latn", type = "reconstructed", } m["poz-mcm-pro"] = { "Proto-Malayo-Chamic", 116773225, "poz-mcm", "Latn", type = "reconstructed", } m["poz-mic-pro"] = { "Proto-Micronesian", 111939079, "poz-mic", "Latn", type = "reconstructed", } m["poz-mly-pro"] = { "Proto-Malayic", 98057728, "poz-mly", "Latn", type = "reconstructed", } m["poz-msa-pro"] = { "Proto-Malayo-Sumbawan", 116773226, "poz-msa", "Latn", type = "reconstructed", } m["poz-nes"] = { "Nese", 2157412, "poz-vnc", "Latn", } m["poz-oce-pro"] = { "Proto-Oceanic", 141741, "poz-oce", "Latn", type = "reconstructed", } m["poz-pcc-pro"] = { "Proto-Central Pacific", 111962726, "poz-pcc", "Latn", type = "reconstructed", } m["poz-pep-pro"] = { "Proto-Eastern Polynesian", 113988745, "poz-pep", "Latn", type = "reconstructed", } m["poz-pnp-pro"] = { "Proto-Nuclear Polynesian", 113988746, "poz-pnp", "Latn", type = "reconstructed", } m["poz-pol-pro"] = { "Proto-Polynesian", 1658709, "poz-pol", "Latn", type = "reconstructed", } m["poz-pro"] = { "Proto-Malayo-Polynesian", 3832960, "poz", "Latn", type = "reconstructed", } m["poz-sml"] = { "Sarawak Malay", 4251702, "poz-mly", "Latn, Arab", } m["poz-ssw-pro"] = { "Proto-South Sulawesi", 116773279, "poz-ssw", "Latn", type = "reconstructed", } m["poz-swa-pro"] = { "Proto-North Sarawak", 116773243, "poz-swa", "Latn", type = "reconstructed", } m["poz-ter"] = { "Terengganu Malay", 4207412, "poz-mly", "Latn, Arab", } m["pqe-pro"] = { "Proto-Eastern Malayo-Polynesian", 2269883, "pqe", "Latn", type = "reconstructed", } m["pra-niy"] = { "Niya Prakrit", 11991601, "inc-mid", "Khar", ancestors = "inc-ash", translit = "Khar-translit", } m["qfa-adm-pro"] = { "Proto-Great Andamanese", 116773756, "qfa-adm", "Latn", type = "reconstructed", } m["qfa-bet-pro"] = { "Proto-Be-Tai", 116773193, "qfa-bet", "Latn", type = "reconstructed", } m["qfa-cka-pro"] = { "Proto-Chukotko-Kamchatkan", 7251837, "qfa-cka", "Latn", type = "reconstructed", } m["qfa-hur-pro"] = { "Proto-Hurro-Urartian", 116773211, "qfa-hur", "Latn", type = "reconstructed", } m["qfa-kad-pro"] = { "Proto-Kadu", 116773770, "qfa-kad", "Latn", type = "reconstructed", } m["qfa-kms-pro"] = { "Proto-Kam-Sui", 55630682, "qfa-kms", "Latn", type = "reconstructed", } m["qfa-kor-pro"] = { "Proto-Koreanic", 467883, "qfa-kor", "Latn", type = "reconstructed", } m["qfa-kra-pro"] = { "Proto-Kra", 7251854, "qfa-kra", "Latn", type = "reconstructed", } m["qfa-lic-pro"] = { "Proto-Hlai", 7251845, "qfa-lic", "Latn", type = "reconstructed", } m["qfa-onb-pro"] = { "Proto-Be", 116773192, "qfa-onb", "Latn", type = "reconstructed", } m["qfa-ong-pro"] = { "Proto-Ongan", 116773801, "qfa-ong", "Latn", type = "reconstructed", } m["qfa-tak-pro"] = { "Proto-Kra-Dai", 104901616, "qfa-tak", "Latn", type = "reconstructed", } m["qfa-yen-pro"] = { "Proto-Yeniseian", 27639, "qfa-yen", "Latn", type = "reconstructed", } m["qfa-yuk-pro"] = { "Proto-Yukaghir", 116773294, "qfa-yuk", "Latn", type = "reconstructed", } m["qwe-kch"] = { "Kichwa", 1740805, "qwe", "Latn", ancestors = "qu", } m["qwe-pro"] = { "Proto-Quechuan", 5575757, "qwe", "Latn", type = "reconstructed", } m["roa-ang"] = { "Angevin", 56782, "roa-oil", "Latn", sort_key = s["roa-oil-sortkey"], } m["roa-bbn"] = { "Bourbonnais-Berrichon", 2899128, "roa-oil", "Latn", sort_key = s["roa-oil-sortkey"], } m["roa-brg"] = { "Bourguignon", 508332, "roa-oil", "Latn", sort_key = s["roa-oil-sortkey"], } m["roa-can"] = { "Cantabrian", 917021, "roa-asl", "Latn", } m["roa-cha"] = { "Champenois", 430018, "roa-oil", "Latn", sort_key = s["roa-oil-sortkey"], } m["roa-fcm"] = { "Franc-Comtois", 510561, "roa-oil", "Latn", sort_key = s["roa-oil-sortkey"], } m["roa-gal"] = { "Gallo", 37300, "roa-oil", "Latn", sort_key = s["roa-oil-sortkey"], } m["roa-gib"] = { "Gallo-Italic of Basilicata", 3094838, "roa-git", ancestors = "pms-old", "Latn", } m["roa-gis"] = { "Gallo-Italic of Sicily", 2629019, "roa-git", "Latn", ancestors = "pms-old", } m["roa-leo"] = { "Leonese", 34108, "roa-asl", "Latn", } m["roa-lor"] = { "Lorrain", 671198, "roa-oil", "Latn", sort_key = s["roa-oil-sortkey"], } m["roa-oca"] = { "Old Catalan", 15478520, "roa-ocr", "Latn", sort_key = {remove_diacritics = c.grave .. c.acute .. c.diaer .. c.cedilla .. "·"}, } m["roa-ole"] = { "Old Leonese", 125977465, "roa-asl", "Latn", } m["roa-ona"] = { "Old Navarro-Aragonese", 2736184, "roa-nar", "Latn", } m["roa-opt"] = { "Old Galician-Portuguese", 1072111, "roa-gap", "Latn", strip_diacritics = {remove_diacritics = c.grave .. c.acute .. c.circ}, } m["roa-orl"] = { "Orléanais", 28497058, "roa-oil", "Latn", sort_key = s["roa-oil-sortkey"], } m["roa-poi"] = { "Poitevin-Saintongeais", 514123, "roa-oil", "Latn", sort_key = s["roa-oil-sortkey"], } m["roa-tar"] = { "Tarantino", 695526, "roa-itr", "Latn", wikimedia_codes = "roa-tara", } m["sai-all"] = { "Allentiac", 19570789, "sai-hrp", "Latn", } m["sai-and"] = { -- not to be confused with 'cbc' or 'ano' "Andoquero", 16828359, "sai-wit", "Latn", } m["sai-ayo"] = { "Ayomán", 16937754, "sai-jir", "Latn", } m["sai-bae"] = { "Baenan", 3401998, "qfa-unc", -- extinct, poorly attested; only known through 9 words "Latn", } m["sai-bag"] = { "Bagua", 5390321, "qfa-unc", -- extinct, poorly attested; possibly Cariban "Latn", } m["sai-bet"] = { "Betoi", 926551, "qfa-iso", "Latn", } m["sai-bor-pro"] = { "Proto-Boran", nil, "sai-bor", "Latn", } m["sai-cac"] = { "Cacán", 945482, "qfa-unc", -- extinct, poorly attested; no consensus on classification "Latn", } m["sai-caq"] = { "Caranqui", 2937753, "sai-bar", "Latn", } m["sai-car-pro"] = { "Proto-Cariban", 116773196, "sai-car", "Latn", type = "reconstructed", } m["sai-cat"] = { "Catacao", 5051136, "sai-ctc", "Latn", } m["sai-cer-pro"] = { "Proto-Cerrado", 116773200, "sai-cer", "Latn", type = "reconstructed", } m["sai-chi"] = { "Chirino", 5390321, "qfa-unc", -- extinct, only four words known; possibly related to Candoshi-Shapra (cbu) "Latn", } m["sai-chn"] = { "Chaná", 5072718, "sai-crn", "Latn", } m["sai-chp"] = { "Chapacura", 5072884, "sai-cpc", "Latn", } m["sai-chr"] = { "Charrua", 5086680, "sai-crn", "Latn", } m["sai-chu"] = { "Churuya", 5118339, "sai-guh", "Latn", } m["sai-cje-pro"] = { "Proto-Central Jê", 116773198, "sai-cje", "Latn", type = "reconstructed", } m["sai-cmg"] = { "Comechingon", 6644203, "qfa-unc", -- extinct, poorly attested; no consensus on classification "Latn", } m["sai-cno"] = { "Chono", 5104704, "qfa-unc", -- extinct, poorly attested; no consensus on classification, possibly spurious "Latn", } m["sai-cnr"] = { "Cañari", 5055572, "qfa-unc", -- extinct, poorly attested; possibly Chimuan or Barbacoan "Latn", } m["sai-coe"] = { "Coeruna", 6425639, "sai-wit", "Latn", } m["sai-col"] = { "Colán", 5141893, "sai-ctc", "Latn", } m["sai-cop"] = { "Copallén", 5390321, "qfa-unc", -- extinct, only four words attested; possibly Cholonan "Latn", } m["sai-crd"] = { "Coroado Puri", 24191321, "sai-mje", "Latn", } m["sai-ctq"] = { "Catuquinaru", 16858455, "qfa-unc", -- extinct, poorly attested; vocabulary does not resemble other languages "Latn", } m["sai-cul"] = { "Culli", 2879660, "qfa-unc", -- extinct, poorly attested; often considered an isolate "Latn", } m["sai-cva"] = { "Cueva", 5192644, "qfa-unc", -- extinct, poorly attested; possibly Chocoan "Latn", } m["sai-esm"] = { "Esmeralda", 3058083, "qfa-unc", -- extinct, poorly attested; possibly related to Yaruro "Latn", } m["sai-ewa"] = { "Ewarhuyana", 16898104, nil, "Latn", } m["sai-gam"] = { "Gamela", 5403661, "qfa-unc", -- extinct, poorly attested; possibly an isolate "Latn", } m["sai-gay"] = { "Gayón", 5528902, "sai-jir", "Latn", } m["sai-gmo"] = { "Guamo", 5613495, "qfa-unc", -- extinct; "Kaufman (1990) finds a connection with the Chapacuran languages convincing." [Wikipedia] Considered an isolate by Campbell (2024). "Latn", } m["sai-gua"] = { "Guachí", 5613172, "sai-guc", "Latn", } m["sai-gue"] = { "Güenoa", 5626799, "sai-crn", "Latn", } m["sai-hau"] = { "Haush", 3128376, "sai-cho", "Latn", } m["sai-jee-pro"] = { "Proto-Jê", 116773212, "sai-jee", "Latn", type = "reconstructed", } m["sai-jko"] = { "Jeikó", 6176527, "sai-mje", "Latn", } m["sai-jrj"] = { "Jirajara", 6202966, "sai-jir", "Latn", } m["sai-kat"] = { -- contrast xoo, kzw, sai-xoc "Katembri", 6375925, "qfa-unc", -- extinct, poorly attested; "Kaufman (1990) has linked it with the nearly extinct Taruma, although this has not been accepted by other scholars." [Wikipedia] "Latn", } m["sai-krp"] = { "Karipuna (Panoan)", 136296616, "sai-pan", "Latn", } m["sai-mal"] = { "Malalí", 6741212, "sai-mje", -- considered the most divergent Maxakalían language (a subdivision of Macro-Jê), for which we have no entry "Latn", } m["sai-mar"] = { "Maratino", 6755055, "qfa-unc", -- extinct, poorly attested; possibly Uto-Aztecan "Latn", } m["sai-mat"] = { "Matanawi", 6786047, "qfa-unc", -- extinct; either an isolate or distantly related to the Muran languages; Campbell (2024) lists it as an isolate, Glottolog gives it as unclassified "Latn", } m["sai-mcn"] = { "Mocana", 3402048, "qfa-unc", -- extinct, poorly attested; given as part of the Malibu languages (geographic grouping; not a clade) "Latn", } m["sai-men"] = { "Menien", 16890110, "sai-mje", "Latn", } m["sai-mil"] = { "Millcayac", 19573012, "sai-hrp", "Latn", } m["sai-mlb"] = { "Malibu", 134374036, "qfa-unc", -- extinct, poorly attested; given as part of the Malibu languages (geographic grouping; not a clade) "Latn", } m["sai-msk"] = { "Masakará", 6782426, "sai-mje", "Latn", } m["sai-muc"] = { "Mucuchí", 6931290, nil, -- generally considered Timotean, for which we have no entry "Latn", } m["sai-mue"] = { "Muellama", 16886936, "sai-bar", "Latn", } m["sai-muz"] = { "Muzo", 6644203, "qfa-unc", -- extinct language of Colombia, poorly attested; may be Pijao (Cariban) "Latn", } m["sai-mys"] = { "Maynas", 16919393, "sai-cah", -- per Campbell (2024); formerly considered unclassified "Latn", } m["sai-nat"] = { "Natú", 9006749, "qfa-unc", -- extinct, poorly attested; "only Greenberg dares to classify [it]".[Wikipedia, quoting Moseley, Christopher; Asher, R. E.; Tait, Mary (1994), Atlas of the world's languages] "Latn", } m["sai-nje-pro"] = { "Proto-Northern Jê", 116773245, "sai-nje", "Latn", type = "reconstructed", } m["sai-opo"] = { "Opón", 7099152, "sai-car", "Latn", } m["sai-oto"] = { "Otomaco", 16879234, "sai-otm", "Latn", } m["sai-pal"] = { "Palta", 3042978, "qfa-unc", -- extinct, unclassified; possibly Chicham "Latn", } m["sai-pam"] = { "Pamigua", 5908689, "sai-tin", "Latn", } m["sai-par"] = { "Paratió", 16890038, "qfa-unc", -- extinct, poorly attested; possibly Xukuruan "Latn", } m["sai-ptx"] = { "Pataxó", 7144304, "sai-mje", "Latn", } m["sai-peb"] = { "Peba", 3373890, "sai-pey", "Latn", } m["sai-pnz"] = { "Panzaleo", 3123275, "qfa-unc", -- extinct, unclassified; possibly Paezan "Latn", } m["sai-prh"] = { "Puruhá", 3410994, "qfa-unc", -- extinct, poorly attested; possibly in a famil with Cañari "Latn", } m["sai-ptg"] = { "Patagón", 128807870, "sai-tar", -- extinct, only known from 4 words, which suggest Cariban lineage (Campbell 2024) "Latn", } m["sai-pur"] = { "Purukotó", 7261622, "sai-pem", "Latn", } m["sai-pyg"] = { "Payaguá", 7156643, "sai-guc", "Latn", } m["sai-pyk"] = { "Pykobjê", 98113977, "sai-nje", "Latn", } m["sai-qmb"] = { "Quimbaya", 7272043, "qfa-unc", -- extinct, might not exist; few known words "Latn", } m["sai-qtm"] = { "Quitemo", 7272651, "sai-cpc", "Latn", } m["sai-rab"] = { "Rabona", 6644203, "qfa-unc", -- extinct, poorly attested, mostly plant names; possibly Candoshi-Shapra "Latn", } m["sai-ram"] = { "Ramanos", 16902824, "qfa-unc", -- extinct, poorly attested, possibly an isolate; per Glottolog: "the minuscule wordlist ... shows no convincing resemblances to surrounding languages" "Latn", } m["sai-sac"] = { "Sácata", 5390321, "qfa-unc", -- extinct, only 3 words known; possibly Candoshí or Arawakan "Latn", } m["sai-san"] = { "Sanaviron", 16895999, "qfa-unc", -- extinct, unclassified; no consensus on classification "Latn", } m["sai-sap"] = { "Sapará", 7420922, "sai-car", "Latn", } m["sai-sec"] = { "Sechura", 7442912, "qfa-unc", -- extinct, poorly attested; possibly Catacaoan "Latn", } m["sai-sin"] = { "Sinúfana", 7525275, "qfa-unc", -- moribund, poorly attested; possibly Chocoan "Latn", } m["sai-sje-pro"] = { "Proto-Southern Jê", 116773814, "sai-sje", "Latn", type = "reconstructed", } m["sai-tab"] = { "Tabancale", 5390321, "qfa-unc", -- extinct, only 5 words known; no obvious connections, might be an isolate "Latn", } m["sai-tal"] = { "Tallán", 16910468, "qfa-unc", -- extinct, poorly attested; might be Catacaoan "Latn", } m["sai-tap"] = { "Tapayuna", 30719984, "sai-nje", "Latn", } m["sai-tar-pro"] = { "Proto-Taranoan", 116773816, "sai-tar", "Latn", type = "reconstructed", } m["sai-teu"] = { "Teushen", 3519243, "qfa-unc", -- probably extinct by the 1950's; possibly Chonan "Latn", } m["sai-tim"] = { "Timote", 7806995, nil, -- possibly in a small Timotean family "Latn", } m["sai-tpr"] = { "Taparita", 7684460, "sai-otm", "Latn", } m["sai-trr"] = { "Tarairiú", 7685313, "qfa-unc", -- extinct, too poorly attested to classify "Latn", } m["sai-wai"] = { "Waitaká", 16918610, "qfa-unc", -- extinct, possibly Purian "Latn", } m["sai-way"] = { "Wayumara", 7960726, "sai-car", "Latn", } m["sai-wit-pro"] = { "Proto-Witotoan", 116773823, "sai-wit", "Latn", type = "reconstructed", } m["sai-wnm"] = { "Wanham", 16879440, "sai-cpc", "Latn", } m["sai-xoc"] = { -- contrast xoo, kzw, sai-kat "Xocó", 12953620, "qfa-unc", -- extinct and poorly attested; not clear if one or three languages "Latn", } m["sai-yao"] = { "Yao (South America)", 16979655, "sai-ven", "Latn", } m["sai-yar"] = { -- not the same family as 'suy' "Yarumá", 3505859, "sai-pek", "Latn", } m["sai-yri"] = { "Yuri", 2669157, "sai-tyu", "Latn", } m["sai-yup"] = { "Yupua", 8061430, "sai-tuc", "Latn", } m["sai-yur"] = { "Yurumanguí", 1281291, "qfa-unc", -- extinct, too poorly attested to classify "Latn", } m["sal-pro"] = { "Proto-Salish", 116773269, "sal", "Latn", type = "reconstructed", } m["sdv-daj-pro"] = { "Proto-Daju", 116773739, "sdv-daj", "Latn", type = "reconstructed", } m["sdv-eje-pro"] = { "Proto-Eastern Jebel", 116773751, "sdv-eje", "Latn", type = "reconstructed", } m["sdv-nil-pro"] = { "Proto-Nilotic", 116773794, "sdv-nil", "Latn", type = "reconstructed", } m["sdv-nyi-pro"] = { "Proto-Nyima", 116773796, "sdv-nyi", "Latn", type = "reconstructed", } m["sdv-tmn-pro"] = { "Proto-Taman", 116773815, "sdv-tmn", "Latn", type = "reconstructed", } m["sel-nor"] = { "Northern Selkup", 30304565, "sel", "Cyrl", translit = "sel-nor-translit", } m["sel-pro"] = { "Proto-Selkup", 128884235, "sel", "Latn", type = "reconstructed", } m["sel-sou"] = { "Southern Selkup", 30304639, "sel", "Cyrl", translit = "sel-sou-translit", } m["sem-amm"] = { "Ammonite", 279181, "sem-can", "Phnx", -- Phnx translit in [[Module:scripts/data]] } m["sem-amo"] = { "Amorite", 35941, "sem-nwe", "Xsux, Latn", } m["sem-cha"] = { "Chaha", 35543, "sem-eth", "Ethi", translit = "Ethi-translit", } m["sem-dad"] = { "Dadanitic", 21838040, "sem-cen", "Narb", -- Narb translit in [[Module:scripts/data]] } m["sem-dum"] = { "Dumaitic", 128810397, "sem-cen", "Narb", -- Narb translit in [[Module:scripts/data]] } m["sem-has"] = { "Hasaitic", 3541433, "sem-cen", "Narb", -- Narb translit in [[Module:scripts/data]] } m["sem-his"] = { "Hismaic", 22948260, "sem-cen", "Narb", -- Narb translit in [[Module:scripts/data]] } m["sem-mhr"] = { "Muher", 33743, "sem-eth", "Latn", } m["sem-pro"] = { "Proto-Semitic", 1658554, "sem", "Latn", type = "reconstructed", } m["sem-saf"] = { "Safaitic", 472586, "sem-cen", "Narb", -- Narb translit in [[Module:scripts/data]] } m["sem-sam"] = { "Samalian", 85847147, "sem-nwe", "Phnx", -- Phnx translit in [[Module:scripts/data]] } m["sem-srb"] = { "Old South Arabian", 35025, "sem-osa", "Sarb", -- Sarb translit in [[Module:scripts/data]] } m["sem-tay"] = { "Taymanitic", 24912301, "sem-cen", "Narb", -- Narb translit in [[Module:scripts/data]] } m["sem-tha"] = { "Thamudic", 843030, "sem-cen", "Narb", -- Narb translit in [[Module:scripts/data]] } m["sem-wes-pro"] = { "Proto-West Semitic", 98021726, "sem-wes", "Latn", type = "reconstructed", } m["sio-pro"] = { -- NB this is not Proto-Siouan-Catawban 'nai-sca-pro' "Proto-Siouan", 34181, "sio", "Latn", type = "reconstructed", } m["sit-aao-pro"] = { "Proto-Central Naga", nil, "sit-aao", "Latn", type = "reconstructed", } m["sit-bai-pro"] = { "Proto-Bai", nil, "sit-bai", "Latn", type = "reconstructed", } m["sit-ban"] = { "Bangru", 56071779, "sit-hrs", "Latn", } m["sit-bdi-pro"] = { "Proto-Bodish", nil, "sit-bdi", "Latn", type = "reconstructed", } m["sit-bok"] = { "Bokar", 4938727, "sit-tan", "Latn, Tibt", override_translit = true, -- Tibt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["sit-cai"] = { "Caijia", 5017528, "sit-cln", "Latn" } m["sit-cha"] = { "Chairel", 5068066, "sit-luu", "Latn", } m["sit-ers-pro"] = { "Proto-Ersuic", nil, "sit-ers", "Latn", type = "reconstructed", } m["sit-hrs-pro"] = { "Proto-Hrusish", 116773762, "sit-hrs", "Latn", type = "reconstructed", } m["sit-jap"] = { "Japhug", 3162245, "sit-egy", "Latn", } m["sit-kha-pro"] = { "Proto-Kham", 116773773, "sit-kha", "Latn", type = "reconstructed", } m["sit-khb-pro"] = { "Proto-Kho-Bwa", nil, "sit-khb", "Latn", type = "reconstructed", } m["sit-khp-pro"] = { "Proto-Puroik", nil, "sit-khb", "Latn", type = "reconstructed", } m["sit-khw-pro"] = { "Proto-Western Kho-Bwa", nil, "sit-khw", "Latn", type = "reconstructed", } m["sit-kon-pro"] = { "Proto-Northern Naga", nil, "sit-kon", "Latn", type = "reconstructed", } m["sit-liz"] = { "Lizu", 6660653, "sit-ers", "Latn", -- and Ersu Shaba } m["sit-lnj"] = { "Longjia", 17096251, "sit-cln", "Latn" } m["sit-lrn"] = { "Luren", 16946370, "sit-cln", "Latn" } m["sit-luu-pro"] = { "Proto-Luish", 116773783, "sit-luu", "Latn", type = "reconstructed", } m["sit-nas-pro"] = { "Proto-Naish", nil, "sit-nas", "Latn", type = "reconstructed", } m["sit-prn"] = { "Puiron", 7259048, "sit-zem", } m["sit-pro"] = { "Proto-Sino-Tibetan", 24839178, "sit", "Latn", type = "reconstructed", } m["sit-sit"] = { "Situ", 19840830, "sit-egy", "Latn", } m["sit-tam-pro"] = { "Proto-Tamangic", 117469295, "sit-tam", "Latn", type = "reconstructed", } m["sit-tan-pro"] = { "Proto-Tani", 116773284, "sit-tan", "Latn", -- needs verification type = "reconstructed", } m["sit-tgm"] = { "Tangam", 17041370, "sit-tan", "Latn", } m["sit-tng-pro"] = { "Proto-Tangkhulic", nil, "sit-tng", "Latn", type = "reconstructed" } m["sit-tos"] = { "Tosu", 7827899, "sit-ers", "Latn", -- also Ersu Shaba } m["sit-tsh"] = { "Tshobdun", 19840950, "sit-egy", "Latn", } m["sit-zbu"] = { "Zbu", 19841106, "sit-egy", "Latn", } m["sla-pro"] = { "Proto-Slavic", 747537, "sla", "Latn", type = "reconstructed", strip_diacritics = { remove_diacritics = c.grave .. c.acute .. c.tilde .. c.macron .. c.dgrave .. c.invbreve, remove_exceptions = {'ś'}, }, sort_key = { from = {"č", "ď", "ě", "ę", "ь", "ľ", "ň", "ǫ", "ř", "š", "ś", "ť", "ъ", "ž"}, to = {"c²", "d²", "e²", "e³", "i²", "l²", "nj", "o²", "r²", "s²", "s³", "t²", "u²", "z²"}, } } m["smi-pro"] = { "Proto-Sami", 7251862, "smi", "Latn", type = "reconstructed", sort_key = { from = {"ā", "č", "δ", "[ëē]", "ŋ", "ń", "ō", "š", "θ", "%([^()]+%)"}, to = {"a", "c²", "d", "e", "n²", "n³", "o", "s²", "t²"} }, } m["son-pro"] = { "Proto-Songhay", 116773277, "son", "Latn", type = "reconstructed", } m["sqj-pro"] = { "Proto-Albanian", 18210846, "sqj", "Latn", type = "reconstructed", } m["ssa-klk-pro"] = { "Proto-Kuliak", 116773779, "ssa-klk", "Latn", type = "reconstructed", } m["ssa-kom-pro"] = { "Proto-Koman", 116773775, "ssa-kom", "Latn", type = "reconstructed", } m["ssa-pro"] = { "Proto-Nilo-Saharan", 116773236, "ssa", "Latn", type = "reconstructed", } m["syd-pro"] = { "Proto-Samoyedic", 7251863, "syd", "Latn", type = "reconstructed", } m["tai-pro"] = { "Proto-Tai", 6583709, "tai", "Latn", type = "reconstructed", } m["tai-swe-pro"] = { "Proto-Southwestern Tai", 116773280, "tai-swe", "Latn", type = "reconstructed", } m["tbq-bdg-pro"] = { "Proto-Bodo-Garo", 116773195, "tbq-bdg", "Latn", type = "reconstructed", } m["tbq-blg"] = { "Bailang", 2879843, "tbq-lob", "Hani", sort_key = "Hani-sortkey", } m["tbq-brm-pro"] = { "Proto-Burmish", nil, "tbq-brm", "Latn", type = "reconstructed", } m["tbq-gkh"] = { "Gokhy", 5578069, "tbq-sil", "Latn", } m["tbq-kuk-pro"] = { "Proto-Kuki-Chin", 116773220, "tbq-kuk", "Latn", type = "reconstructed", } m["tbq-lal-pro"] = { "Proto-Lalo", 116773781, "tbq-lal", "Latn", type = "reconstructed", } m["tbq-laz"] = { "Laze", 17007626, "sit-nas", "Latn", } m["tbq-lob-pro"] = { "Proto-Lolo-Burmese", 116773224, "tbq-lob", "Latn", type = "reconstructed", } m["tbq-lol-pro"] = { "Proto-Loloish", 7251855, "tbq-lol", "Latn", type = "reconstructed", } m["tbq-mil"] = { "Milang", 6850761, "sit-gsi", "Deva, Latn", } m["tbq-mor"] = { "Moran", 6909216, "tbq-bdg", "Latn", } m["tbq-ngo"] = { "Ngochang", 56582, "tbq-brm", "Latn", } -- tbq-pro is now etymology-only m["trk-dkh"] = { "Dukhan", 12809273, "trk-ssb", "Latn, Cyrl, Mong", -- Mong translit, display_text and strip_diacritics in [[Module:scripts/data]] } -- As described in Mahmud al-Kashgari's 11th century ''Dīwān Lughāt al-Turk''. m["trk-eog"] = { "Early Old Oghuz", nil, "trk-ogz", "Arab", strip_diacritics = {Arab = "ar-stripdiacritics"}, } m["trk-oat"] = { "Old Anatolian Turkish", 7083390, "trk-ogz", "Arab", strip_diacritics = {Arab = "ar-stripdiacritics"}, ancestors = "trk-eog", } m["trk-pro"] = { "Proto-Turkic", 3657773, "trk", "Latn", type = "reconstructed", standard_chars = { Latn = " ()-abdegiklmnoprstuxyzïöüāčēīĺŋōŕšūǖȫẹ" .. c.macron, } } m["tup-gua-pro"] = { "Proto-Tupi-Guarani", 116773288, "tup-gua", "Latn", type = "reconstructed", } m["tup-kab"] = { "Kabishiana", 15302988, "tup", "Latn", } m["tup-kaw"] = { "Kawahiva", 6346712, "tup-gua", "Latn", } m["tup-pro"] = { "Proto-Tupian", 10354700, "tup", "Latn", type = "reconstructed", } m["tuw-alk"] = { "Alchuka", 113553616, "tuw-jrc", "Latn, Hans", sort_key = {Hans = "Hani-sortkey"}, } m["tuw-bal"] = { "Bala", 86730632, "tuw-jrc", "Latn, Hans", sort_key = {Hans = "Hani-sortkey"}, } m["tuw-kkl"] = { "Kyakala", 118875708, "tuw-jrc", "Latn, Hans", sort_key = {Hans = "Hani-sortkey"}, } m["tuw-kli"] = { "Kili", 6406892, "tuw-ewe", "Cyrl", } m["tuw-pro"] = { "Proto-Tungusic", 85872335, "tuw", "Latn", type = "reconstructed", } m["tuw-sol"] = { "Solon", 30004, "tuw-ewe", } m["urj-fin-pro"] = { "Proto-Finnic", 11883720, "urj-fin", "Latn", type = "reconstructed", } m["urj-koo"] = { "Old Komi", 86679962, "kv", "Perm, Cyrs", translit = "urj-koo-translit", -- Cyrs strip_diacritics, sort_key in [[Module:scripts/data]]; previously, Cyrs strip_diacritics not present } m["urj-kuk"] = { "Kukkuzi", 107410460, "urj-fin", "Latn", ancestors = "vot", } m["urj-kya"] = { "Komi-Yazva", 2365210, "kv", "Cyrl", translit = "kv-translit", override_translit = true, strip_diacritics = {remove_diacritics = c.acute}, } m["urj-mdv-pro"] = { "Proto-Mordvinic", 116773232, "urj-mdv", "Latn", type = "reconstructed", } m["urj-prm-pro"] = { "Proto-Permic", 116773257, "urj-prm", "Latn", type = "reconstructed", } m["urj-pro"] = { "Proto-Uralic", 288765, "urj", "Latn", type = "reconstructed", } m["urj-ugr-pro"] = { "Proto-Ugric", 156631, "urj-ugr", "Latn", type = "reconstructed", } m["xnd-pro"] = { "Proto-Na-Dene", 116773233, "xnd", "Latn", type = "reconstructed", } m["xgn-pro"] = { "Proto-Mongolic", 2493677, "xgn", "Latn", type = "reconstructed", sort_key = { from = {"č", "i", "ï", "ǰ", "ŋ", "ö", "š", "ü"}, to = {"c", "i" .. p[1], "i", "j", "n" .. p[1], "o" .. p[1], "s" .. p[1], "u" .. p[1]}, }, } m["yok-bvy"] = { "Buena Vista Yokuts", 4985474, "yok", "Latn", } m["yok-dly"] = { "Delta Yokuts", 70923266, "yok", "Latn", } m["yok-gsy"] = { "Gashowu Yokuts", 3098708, "yok", "Latn", } m["yok-kry"] = { "Kings River Yokuts", 6413014, "yok", "Latn", } m["yok-nvy"] = { "Northern Valley Yokuts", 85789777, "yok", "Latn", } m["yok-ply"] = { "Palewyami Yokuts", 2387391, "yok", "Latn", } m["yok-svy"] = { "Southern Valley Yokuts", 12642473, "yok", "Latn", } m["yok-tky"] = { "Tule-Kaweah Yokuts", 7851988, "yok", "Latn", } m["ypk-pro"] = { "Proto-Yupik", 116773295, "ypk", "Latn", type = "reconstructed", display_text = s["esx-displaytext"], strip_diacritics = s["esx-stripdiacritics"], } m["yrk-for"] = { "Forest Nenets", 1295107, "yrk", "Cyrl", translit = "yrk-for-translit", strip_diacritics = {remove_diacritics = c.grave .. c.acute .. c.macron .. c.breve .. c.dotabove}, } m["yrk-tun"] = { "Tundra Nenets", 36452, "yrk", "Cyrl", strip_diacritics = { from = {"ӑ", "а̄", "э̇", "ӣ", "ы̄", "ӯ", "ю̄", "я̆", "я̄"}, to = {"а", "а", "э", "и", "ы", "у", "ю", "я", "я"}, }, translit = "yrk-tun-translit", } m["zhx-min-pro"] = { "Proto-Min", 19646347, "zhx-min", "Latn", type = "reconstructed", } m["zhx-sht"] = { "Shaozhou Tuhua", 1920769, "zhx", "Nshu, Hants", generate_alternants = "zh-generatealternants", sort_key = {Hani = "Hani-sortkey"}, } m["zhx-sic"] = { "Sichuanese", 2278732, "zhx-man", "Hants", generate_alternants = "zh-generatealternants", translit = "zh-translit", sort_key = "Hani-sortkey", } m["zhx-tai"] = { "Taishanese", 2208940, "zhx-yue", "Hants", generate_alternants = "zh-generatealternants", translit = "zh-translit", sort_key = "Hani-sortkey", } m["zle-ono"] = { "Old Novgorodian", 162013, "zle", "Cyrs, Glag", translit = {Cyrs = "Cyrs-translit", Glag = "Glag-translit"}, -- Cyrs strip_diacritics, sort_key in [[Module:scripts/data]] } m["zle-ort"] = { "Old Ruthenian", 13211, "zle", "Arab, Cyrs, Latn", ancestors = "orv", translit = { Cyrs = "zle-ort-translit", Arab = "zle-ort-Arab-translit", }, strip_diacritics = { Cyrs = { remove_diacritics = m_langdata.chars_substitutions["Cyrs_remove_diacritics"], remove_exceptions = {"Ї", "ї"}, }, Arab = "ar-stripdiacritics", }, -- Cyrs sort_key in [[Module:scripts/data]] } m["zls-chs"] = { "Church Slavonic", 33251, "zls", "Cyrs, Glag, Latn", ancestors = "cu", translit = { Cyrs = "Cyrs-translit", Glag = "Glag-translit" }, -- Cyrs strip_diacritics, sort_key in [[Module:scripts/data]] } m["zlw-ocs"] = { "Old Czech", 593096, "zlw", "Latn", } m["zlw-opl"] = { "Old Polish", 149838, "zlw-lch", "Latn", strip_diacritics = {remove_diacritics = c.ringabove}, } m["zlw-osk"] = { "Old Slovak", 12776676, "zlw", "Latn", } m["zlw-slv"] = { "Slovincian", 36822, "zlw-pom", "Latn", strip_diacritics = {remove_diacritics = c.macron .. c.breve}, } return require("Module:tungumál").finalizeData(m, "language") pzs7yjgn7do531ixcgu110br14yuhhi Module:tungumál/gögn/2/extra 828 119304 393955 2026-10-09T05:41:05Z Ow! That Hurts! 8952 skapað 393955 Scribunto text/plain local m = {} m["aa"] = { aliases = {"Qafar"}, } m["ab"] = { aliases = {"Abkhazian", "Abxazo"}, } m["ae"] = { aliases = {"Zend", "Old Bactrian"}, } m["af"] = { } m["ak"] = { varieties = {"Twi-Fante", "Twi", {"Fante", "Fanti"}, "Asante", "Akuapem", "Abron", "Brong", "Wasa"}, } m["am"] = { } m["an"] = { } m["ar"] = { -- FIXME, some of the following are varieties but it's not clear which ones aliases = {"Standard Arabic", "Literary Arabic", "High Arabic"}, varieties = {"Modern Standard Arabic", "Classical Arabic", "Judeo-Arabic"}, } m["as"] = { aliases = {"Asamiya"}, } m["av"] = { aliases = {"Avaric"}, } m["ay"] = { varieties = {"Southern Aymara", "Central Aymara"}, } m["az"] = { aliases = {"Azeri", "Azari", "Azeri Turkic", "Azerbaijani Turkic"}, varieties = {"North Azerbaijani", "South Azerbaijani", {"Afshar", "Afshari", "Afshar Azerbaijani", "Afchar"}, {"Qashqa'i", "Qashqai", "Kashkay"}, "Sonqor" }, } m["ba"] = { } m["be"] = { aliases = {"Belorussian", "Belarusan", "Bielorussian", "Byelorussian", "Belarussian", "White Russian"}, } m["bg"] = { } m["bh"] = { } m["bi"] = { } m["bm"] = { aliases = {"Bamanankan"}, } m["bn"] = { aliases = {"Bangla"}, } m["bo"] = { varieties = { {"Amdo Tibetan", "Amdo"}, "Dolpo", {"Khams", "Khams Tibetan"}, "Khamba", "Gola", "Humla", "Limi", {"Lhasa", "Lhasa Tibetan"}, "Lhomi", "Loke", "Lowa", "Mugom", "Mugu", "Mustang", "Nubri", "Panang", "Shing Saapa", "Thudam", "Tichurong", "Tseku", {"Ü", "Dbus"}, "Walungge"}, -- and "Gyalsumdo", "Lower Manang"? "Kyirong"? } m["br"] = { varieties = {{"Gwenedeg", "Vannetais"}, {"Kerneveg", "Cornouaillais"}, {"Leoneg", "Léonard"}, {"Tregerieg", "Trégorrois"}}, } m["ca"] = { -- don't list varieties here that are in [[Module:etymology languages/data]] } m["ce"] = { } m["ch"] = { aliases = {"Chamoru"}, } m["co"] = { aliases = {"Corsu"}, } m["cr"] = { } m["cs"] = { } m["cu"] = { aliases = {"Old Church Slavic"}, } m["cv"] = { } m["cy"] = { varieties = {"Cofi Welsh", {"Dyfedeg", "Dyfed Welsh", "Demetian"}, {"Gwenhwyseg", "Gwent Welsh", "Gwentian"}, {"Gwyndodeg", "Gwynedd Welsh", "Venedotian"}, {"Powyseg", "Powys Welsh", "Powysian"}, "Patagonian Welsh"}, } m["da"] = { } m["de"] = { aliases = {"High German", "New High German", "Deutsch"}, varieties = {"Alsatian German", "American German", "Bavarian German", "Belgian German", "Central German", "DDR German", "East African German", "German German", "Hessian German", "Indiana German", "Liechtenstein German", "Lorraine German", "Luxembourgish German", "Namibian German", "Northern German", "Prussian German", "Silesia German", "South African German", "Southern German", "South Tyrolean German", "Switzerland German", "Texan German"}, } m["dv"] = { aliases = {"Divehi", "Maldivian"}, varieties = {{"Mahal", "Mahl"}}, } m["dz"] = { } m["ee"] = { } m["el"] = { aliases = {"Modern Greek", "Neo-Hellenic"}, } m["en"] = { aliases = {"Modern English", "New English"}, varieties = {"Polari", "Yinglish"}, } m["eo"] = { } m["es"] = { aliases = {"Castilian"}, varieties = {{"Amazonian Spanish", "Amazonic Spanish"}, "Loreto-Ucayali Spanish"}, } m["et"] = { } m["eu"] = { aliases = {"Euskara"}, } m["fa"] = { aliases = {"Farsi", "New Persian", "Modern Persian"}, varieties = {{"Western Persian", "Iranian Persian"}, {"Eastern Persian", "Dari"}, {"Aimaq", "Aimak", "Aymaq", "Eimak"}}, } m["ff"] = { aliases = {"Fulani"}, varieties = {"Adamawa Fulfulde", "Bagirmi Fulfulde", "Borgu Fulfulde", "Central-Eastern Niger Fulfulde", "Fulfulde", "Maasina Fulfulde", "Nigerian Fulfulde", "Pular", "Pulaar", "Western Niger Fulfulde"}, -- Maasina, etc are dialects, subsumed into this code; Pular and Pulaar are distinct } m["fi"] = { aliases = {"Suomi"}, } m["fj"] = { } m["fo"] = { aliases = {"Faeroese"}, } m["fr"] = { aliases = {"Modern French"}, varieties = {"African French", "Algerian French", "Alsatian French", "Antilles French", "Atlantic Canadian French", "Belgian French", "Congolese French", "European French", "French French", "Haitian French", "Ivorian French", "Lorraine French", "Louisiana French", "Luxembourgish French", "Malian French", "Marseille French", "Missourian French", "Moroccan French", "Newfoundland French", "North American French", "Picard French", "Provençal French", "Quebec French", "Réunion French", "Rwandan French", "Tunisian French", "West African French"}, } m["fy"] = { aliases = {"Western Frisian"}, } m["ga"] = { aliases = {"Irish Gaelic", "Gaelic"}, -- calling it simply "Gaelic" is rare in Ireland, but relatively common in the Irish diaspora varieties = {{"Cois Fharraige Irish", "Cois Fhairrge Irish"}, {"Connacht Irish", "Connaught Irish"}, "Cork Irish", "Donegal Irish", "Galway Irish", "Kerry Irish", "Mayo Irish", "Munster Irish", "Ulster Irish", "Waterford Irish", "West Muskerry Irish"}, } m["gd"] = { aliases = {"Gaelic", "Gàidhlig", "Scots Gaelic", "Scottish"}, varieties = {"Argyll Gaelic", "Arran Scottish Gaelic", {"Canadian Gaelic", "Canadian Scottish Gaelic", "Cape Breton Gaelic"}, "East Sutherland Gaelic", {"Galwegian Gaelic", "Gallovidian Gaelic", "Gallowegian Gaelic", "Galloway Gaelic"}, "Hebridean Gaelic", "Highland Gaelic"}, } m["gl"] = { } m["gu"] = { } m["gv"] = { aliases = {"Manx Gaelic"}, varieties = {"Northern Manx", "Southern Manx"}, } m["ha"] = { } m["he"] = { aliases = {"Ivrit"}, pseudo_families = "qfa-jew", } m["hi"] = { other_names = {"Hindavi"}, } m["ho"] = { aliases = {"Pidgin Motu", "Police Motu"}, } m["ht"] = { aliases = {"Creole", "Haitian", "Kreyòl"}, } m["hu"] = { aliases = {"Magyar"}, } m["hy"] = { aliases = {"Modern Armenian"}, varieties = {"Eastern Armenian", "Western Armenian"}, } m["hz"] = { } m["ia"] = { } m["id"] = { } m["ie"] = { aliases = {"Occidental"}, } m["ig"] = { } m["ii"] = { aliases = { "Nosu", "Nuosu Yi", "Nosu Yi", "Sichuan Yi", "Northern Yi", "Liangshan Yi", }, } m["ik"] = { aliases = {"Inupiak", "Iñupiaq", "Inupiatun"}, } m["io"] = { } m["is"] = { } m["it"] = { } m["iu"] = { varieties = { "Aivilimmiut", {"Eastern Canadian Inuktitut", "Eastern Canadian Inuit"}, {"Inuinnaq", "Inuinnaqtun"}, {"Inuvialuktun", "Inuvialuk", "Western Canadian Inuktitut", "Western Canadian Inuit", "Western Canadian Inuktun"}, "Kivallirmiut", "Natsilingmiut", "Nunavimmiutit", "Nunatsiavummiut", {"Siglitun", "Siglit"}}, } m["ja"] = { aliases = {"Modern Japanese", "Nipponese", "Nihongo"}, } m["jv"] = { } m["ka"] = { varieties = {{"Judeo-Georgian", "Kivruli", "Gruzinic"}}, } m["kg"] = { aliases = {"Kikongo"}, varieties = {"Koongo", "Laari", "San Salvador Kongo", "Yombe"}, } m["ki"] = { aliases = {"Gikuyu", "Gĩkũyũ"}, } m["kj"] = { aliases = {"Kuanyama", "Oshikwanyama"}, } m["kk"] = { } m["kl"] = { aliases = {"Kalaallisut"}, } m["km"] = { aliases = {"Cambodian", "Central Khmer", "Modern Khmer"}, } m["kn"] = { } m["ko"] = { aliases = {"Modern Korean"}, } m["kr"] = { varieties = {"Kanembu", "Bilma Kanuri", "Central Kanuri", "Manga Kanuri", "Tumari Kanuri"}, } m["ks"] = { aliases = {"Koshur"}, } -- "kv" is treated as "koi", "kpv", see [[WT:LT]] m["kw"] = { } m["ky"] = { aliases = {"Kirghiz", "Kirgiz"}, } m["la"] = { } m["lb"] = { } m["lg"] = { aliases = {"Ganda", "Oluganda"}, } m["li"] = { aliases = {"Limburgan", "Limburgian", "Limburgic"}, } m["ln"] = { aliases = {"Ngala"}, } m["lo"] = { aliases = {"Laotian"}, } m["lt"] = { } m["lu"] = { } m["lv"] = { aliases = {"Lettish", "Lett"}, } m["mg"] = { varieties = { {"Antankarana", "Antankarana Malagasy"}, {"Bara Malagasy", "Bara"}, {"Betsimisaraka Malagasy", "Betsimisaraka"}, {"Northern Betsimisaraka Malagasy", "Northern Betsimisaraka"}, {"Southern Betsimisaraka Malagasy", "Southern Betsimisaraka"}, {"Bushi", "Shibushi", "Kibushi"}, {"Masikoro Malagasy", "Masikoro"}, "Plateau Malagasy", "Sakalava", {"Tandroy Malagasy", "Tandroy"}, {"Tanosy", "Tanosy Malagasy"}, "Tesaka", {"Tsimihety", "Tsimihety Malagasy"}}, } m["mh"] = { } m["mi"] = { aliases = {"Maori"}, } m["mk"] = { } m["ml"] = { } m["mn"] = { varieties = {"Khalkha Mongolian"}, } -- "mo" is treated as "ro", see [[WT:LT]] m["mr"] = { } m["ms"] = { aliases = {"Malaysian", "Standard Malay"}, } m["mt"] = { } m["my"] = { aliases = {"Myanmar"}, varieties = {"Mandalay Burmese", "Myeik Burmese", "Palaw Burmese", {"Rangoon Burmese", "Yangon Burmese"}, "Yaw Burmese"}, } m["na"] = { aliases = {"Nauru"}, } m["nb"] = { aliases = {"Bokmål"}, } m["nd"] = { aliases = {"North Ndebele"}, } m["ne"] = { aliases = {"Nepalese"}, varieties = {"Palpa"}, -- 3832956, former "plp", retired by ISO as spurious } m["ng"] = { } m["nl"] = { varieties = {"Netherlandic", "Flemish"}, -- FIXME, check this } m["nn"] = { aliases = {"New Norwegian", "Nynorsk"}, } m["no"] = { } m["nr"] = { aliases = {"South Ndebele"}, } m["nv"] = { aliases = {"Navaho", "Diné bizaad"}, } m["ny"] = { aliases = {"Chicheŵa", "Chinyanja", "Nyanja", "Chewa", "Cicewa", "Cewa", "Cinyanja"}, } m["oc"] = { -- don't list varieties here that are in [[Module:etymology languages/data]] } m["oj"] = { aliases = {"Ojibway", "Ojibwa"}, varieties = {{"Chippewa", "Ojibwemowin", "Southwestern Ojibwa"}}, } m["om"] = { varieties = {"Orma", "Borana-Arsi-Guji Oromo", "West Central Oromo"}, } m["or"] = { aliases = {"Oriya", "Oorya"}, } m["os"] = { aliases = {"Ossete", "Ossetic"}, varieties = {"Digor", "Iron"}, } m["pa"] = { aliases = {"Panjabi"}, } m["pi"] = { } m["pl"] = { } m["ps"] = { aliases = {"Pashtun", "Pushto", "Pashtu", "Afghani"}, varieties = {"Central Pashto", "Northern Pashto", "Southern Pashto", {"Pukhto", "Pakhto", "Pakkhto"}}, } m["pt"] = { aliases = {"Modern Portuguese"}, } m["qu"] = { } m["rm"] = { aliases = {"Romansch", "Rumantsch", "Romanche"}, -- don't list varieties here that are in [[Module:etymology languages/data]] varieties = {"Sursilvan-Oberland"}, -- extra variety in Glottolog; main varieties have their own etym codes } m["ro"] = { aliases = {"Daco-Romanian", "Roumanian", "Rumanian"}, } m["ru"] = { } m["rw"] = { -- don't list varieties here that are in [[Module:etymology languages/data]] varieties = {{"Ha", "Giha"}, "Hangaza", "Vinza", "Shubi"}, -- Deleted "Subi", which normally refers to a different language } m["sa"] = { } m["sc"] = { -- don't list varieties here that are in [[Module:etymology languages/data]] } m["sd"] = { } m["se"] = { aliases = {"North Sami", "Northern Saami", "North Saami"}, } m["sg"] = { } m["sh"] = { aliases = {"BCS", "Croato-Serbian", "Serbocroatian"}, -- don't list varieties here that are in [[Module:etymology languages/data]] varieties = {"Bosnian", "Croatian", "Montenegrin", "Serbian", "Shtokavian"}, } m["si"] = { aliases = {"Singhalese", "Sinhala"}, } m["sk"] = { } m["sl"] = { aliases = {"Slovenian"}, } m["sm"] = { } m["sn"] = { } m["so"] = { } m["sq"] = { -- don't list varieties here that are in [[Module:etymology languages/data]] } m["ss"] = { aliases = {"Swati"}, } m["st"] = { aliases = {"Sesotho", "Southern Sesotho", "Southern Sotho"}, } m["su"] = { } m["sv"] = { } m["sw"] = { varieties = {{"Settler Swahili", "KiSetla", "KiSettla", "Setla", "Settla", "Kitchen Swahili"}, {"Kihindi", "Indian Swahili"}, {"KiShamba", "Kishamba", "Field Swahili"}, {"Kibabu", "Asian Swahili"}, {"Kimanga", "Arab Swahili"}, {"Kitvita", "Army Swahili"}}, } m["ta"] = { } m["te"] = { } m["tg"] = { aliases = {"Eastern Persian", "Tadjik", "Tadzhik", "Tajiki", "Tajik Persian", "Tajiki Persian"}, } m["th"] = { aliases = {"Central Thai", "Siamese"}, } m["ti"] = { aliases = {"Tigrigna"}, } m["tk"] = { } m["tl"] = { } m["tn"] = { aliases = {"Setswana"}, } m["to"] = { } m["tr"] = { } m["ts"] = { aliases = {"Xitsonga"}, } m["tt"] = { } -- "tw" is treated as "ak", see [[WT:LT]] m["ty"] = { } m["ug"] = { aliases = {"Uigur", "Uighur", "Uygur"}, } m["uk"] = { } m["ur"] = { } m["uz"] = { varieties = {"Northern Uzbek", "Southern Uzbek"}, } m["ve"] = { } m["vi"] = { aliases = {"Annamese", "Annamite"}, } m["vo"] = { } m["wa"] = { varieties = {"Liégeois", "Namurois", "Wallo-Picard", "Wallo-Lorrain"}, } m["wo"] = { varieties = {"Gambian Wolof"}, -- the subsumed dialect 'wof' } m["xh"] = { } m["yi"] = { varieties = {"American Yiddish", "Daytshmerish Yiddish", "Mideastern Yiddish", "Galitzish", {"Northeastern Yiddish", "Litvish", "Lithuanian Yiddish"}, {"Northwestern Yiddish", "Netherlandic Yiddish"}, {"Polish Yiddish", "Poylish"}, "South African Yiddish", {"Southeastern Yiddish", "Ukrainian Yiddish", "Ukrainish"}, {"Southwestern Yiddish", "Judeo-Alsatian"}, "Udmurtish" }, pseudo_families = "qfa-jew", } m["yo"] = { } m["za"] = { -- FIXME, are all of the following distinct? varieties = { "Chongzuo Zhuang", "Guibei Zhuang", "Guibian Zhuang", "Central Hongshuihe Zhuang", "Eastern Hongshuihe Zhuang", "Lianshan Zhuang", "Liujiang Zhuang", "Liuqian Zhuang", {"Min Zhuang", "Minz Zhuang"}, "Nong Zhuang", -- see zhn "Qiubei Zhuang", "Shangsi Zhuang", {"Dai Zhuang", "Wenma", "Wenma Thu", "Wenma Zhuang"}, "Yang Zhuang", {"Yongbei Zhuang", "Wuming Zhuang", "Standard Zhuang"}, "Yongnan Zhuang", "Youjiang Zhuang", "Zuojiang Zhuang"}, } m["zh"] = { } m["zu"] = { aliases = {"isiZulu"}, } return m i1vikwgeuqfr31dis461es462cudmg3 Module:tungumál/gögn/3/a 828 119305 393957 2026-10-09T05:44:46Z Ow! That Hurts! 8952 skapað 393957 Scribunto text/plain local m_langdata = require("Module:tungumál/gögn") -- Loaded on demand, as it may not be needed (depending on the data). local function u(...) u = require("Module:string utilities").char return u(...) end local c = m_langdata.chars local p = m_langdata.puaChars local s = m_langdata.shared local m = {} m["aaa"] = { "Ghotuo", 35463, "alv-yek", "Latn", } m["aab"] = { "Alumu-Tesu", 35034, "nic-alu", "Latn", } m["aac"] = { "Ari", 1811224, "ngf-gsu", "Latn", } m["aad"] = { "Amal", 56708, "paa-sep", "Latn", } -- "aae" is treated as "sq", see [[WT:LT]] m["aaf"] = { "Aranadan", 3507928, "dra-mal", "Mlym", -- Mlym translit in [[Module:scripts/data]] (NOTE: not present before, presumably an accidental omission) } m["aag"] = { "Ambrak", 4741706, "paa-pal", "Latn", } m["aah"] = { "Abu'", 4670715, "paa-ara", "Latn", } m["aai"] = { "Arifama-Miniafia", 4790560, "poz-ocw", "Latn", } m["aak"] = { "Ankave", 3446690, "ngf-ata", "Latn", } m["aal"] = { "Afade", 56434, "cdc-cbm", "Latn", } m["aan"] = { "Anambé", 3507873, "tup-gua", "Latn", } m["aap"] = { "Arára (Pará)", 56807, "sai-pek", "Latn", } m["aaq"] = { "Penobscot", 3515185, "alg-abp", "Latn", } m["aas"] = { "Aasax", 56620, "cus-sou", "Latn", } -- "aat" is treated as "sq", see [[WT:LT]] m["aau"] = { "Abau", 3073568, "paa-sep", "Latn", } m["aaw"] = { "Solong", 7558834, "poz-ocw", "Latn", } m["aax"] = { "Upper Mandobo", 12636156, "ngf-dum", "Latn", } m["aaz"] = { "Amarasi", 4740192, "poz-tim", "Latn", } m["aba"] = { "Abé", 34833, "alv-lag", "Latn", } m["abb"] = { "Bankon", 34860, "bnt-bsa", "Latn", } m["abc"] = { "Ambala Ayta", 3448896, "phi", "Latn", } m["abd"] = { "Camarines Norte Agta", 3399682, "phi", "Latn", } m["abe"] = { "Abenaki", 17502788, "alg-abp", "Latn", } m["abf"] = { "Abai Sungai", 4663287, "poz-san", "Latn", } m["abg"] = { "Abaga", 3507954, "ngf-kya", "Latn", } m["abh"] = { "Tajiki Arabic", 56833, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["abi"] = { "Abidji", 34781, "alv-lag", "Latn", } m["abj"] = { "Aka-Bea", 2356391, "qfa-ads", "Latn", } m["abl"] = { "Abung", 49215, "poz-lgx", "Latn", } m["abm"] = { "Abanyom", 7502, "nic-eko", "Latn", } m["abn"] = { "Abua", 34835, "nic-cde", "Latn", } m["abo"] = { "Abon", 35121, "nic-tvn", "Latn", } m["abp"] = { "Abenlen Ayta", 3436621, "phi", "Latn", } m["abq"] = { "Abaza", 27567, "cau-abz", "Cyrl, Latn", translit = { Cyrl = "abq-translit" }, override_translit = true, display_text = { Cyrl = s["cau-Cyrl-displaytext"] }, strip_diacritics = { Cyrl = s["cau-Cyrl-stripdiacritics"], Latn = s["cau-Latn-stripdiacritics"], }, sort_key = { Cyrl = { from = { "гъв", "гъь", "гӏв", "джв", "джь", "къв", "къь", "кӏв", "кӏь", "хъв", "хӏв", "чӏв", -- 3 chars "гв", "гъ", "гь", "гӏ", "дж", "дз", "ё", "жв", "жь", "кв", "къ", "кь", "кӏ", "ль", "лӏ", "пӏ", "тл", "тш", "тӏ", "фӏ", "хв", "хъ", "хь", "хӏ", "цӏ", "чв", "чӏ", "шв", "шӏ" -- 2 chars }, to = { "г" .. p[3], "г" .. p[4], "г" .. p[7], "д" .. p[2], "д" .. p[3], "к" .. p[3], "к" .. p[4], "к" .. p[7], "к" .. p[8], "х" .. p[3], "х" .. p[6], "ч" .. p[3], "г" .. p[1], "г" .. p[2], "г" .. p[5], "г" .. p[6], "д" .. p[1], "д" .. p[4], "е" .. p[1], "ж" .. p[1], "ж" .. p[2], "к" .. p[1], "к" .. p[2], "к" .. p[5], "к" .. p[6], "л" .. p[1], "л" .. p[2], "п" .. p[1], "т" .. p[1], "т" .. p[2], "т" .. p[3], "ф" .. p[1], "х" .. p[1], "х" .. p[2], "х" .. p[4], "х" .. p[5], "ц" .. p[1], "ч" .. p[1], "ч" .. p[2], "ш" .. p[1], "ш" .. p[2] } }, }, } -- "abr" Abron is treated as "ak" Akan, see [[WT:LT]] m["abs"] = { "Ambonese Malay", 3124354, "crp", "Latn", ancestors = "ms", } m["abt"] = { "Ambulas", 3508015, "paa-nnd", "Latn", } m["abu"] = { "Abure", 34767, "alv-ptn", "Latn", } m["abv"] = { "Baharna Arabic", 56576, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["abw"] = { "Pal", 7126121, "ngf-omo", "Latn", } m["abx"] = { "Inabaknon", 2820163, "poz-sbj", "Latn", } m["aby"] = { "Aneme Wake", 3508107, "ngf-yar", "Latn", } m["abz"] = { "Abui", 2822110, "paa-alp", "Latn", } m["aca"] = { "Achagua", 2822982, "awd", "Latn", } m["acb"] = { "Áncá", 11130787, "nic-mom", "Latn", } m["acd"] = { "Gikyode", 35256, "alv-gng", "Latn", } m["ace"] = { "Acehnese", 27683, "cmc", "Latn, Arab", standard_chars = { Latn = "AaBbCcDdEeÉéÈèËëFfGgHhIiJjKkLlMmNnOoÔôÖöPpQqRrSsTtUuVvWwXxYyZz", -- current orthography (not yet add Arab) c.punc }, } m["ach"] = { "Acholi", 34926, "sdv-los", "Latn", } m["aci"] = { "Aka-Cari", 2670418, "qfa-adn", "Latn", } m["ack"] = { "Aka-Kora", 3433680, "qfa-adn", "Latn", } m["acl"] = { "Akar-Bale", 3436825, "qfa-ads", "Latn", } m["acm"] = { "Iraqi Arabic", 56232, "sem-arb", "Arab, Hebr", strip_diacritics = { Arab = "ar-stripdiacritics", }, -- Hebr display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["acn"] = { "Achang", 56582, "tbq-brm", "Latn", } m["acp"] = { "Eastern Acipa", 5329945, "nic-kmk", "Latn", } m["acr"] = { "Achi", 34774, "myn", "Latn", } m["acs"] = { "Acroá", 2829146, "sai-cje", "Latn", } m["acu"] = { "Achuar", 2823170, "sai-jiv", "Latn", } m["acv"] = { "Achumawi", 56661, "nai-pal", "Latn", } m["acw"] = { "Hijazi Arabic", 56608, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["acx"] = { "Omani Arabic", 56630, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["acy"] = { "Cypriot Arabic", 56416, "sem-arb", "Latn, Grek", ancestors = "acm", strip_diacritics = { Latn = {remove_diacritics = c.grave .. c.acute .. c.breve}, }, -- Grek display_text, strip_diacritics, sort_key in [[Module:scripts/data]] standard_chars = { Latn = "AaBbCcDdΔδEeFfGgĠġĊċIiJjKkLlMmNnOoPpΘθRrSsTtUuVvWwXxYyZzŞş", c.punc }, } m["acz"] = { "Acheron", 34769, "alv-tal", "Latn", } m["ada"] = { "Adangme", 35141, "alv-gda", "Latn", } m["adb"] = { "Atauran", 125421255, "poz-cet", "Latn", } m["add"] = { "Dzodinka", 35266, "nic-nka", "Latn", } m["ade"] = { "Adele", 27740, "alv-ntg", "Latn", } m["adf"] = { "Dhofari Arabic", 56565, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["adg"] = { "Andegerebinha", 3508123, "aus-rnd", "Latn", } m["adh"] = { "Adhola", 1971400, "sdv-los", "Latn", } m["adi"] = { "Adi", 56440, "sit-tan", "Latn", } m["adj"] = { "Adioukrou", 34738, "alv-lag", "Latn", } m["adl"] = { "Galo", 2857892, "sit-tan", "Latn", } m["adn"] = { "Adang", 3398276, "paa-alp", "Latn", } m["ado"] = { "Abu", 56659, "paa-por", "Latn", } m["adp"] = { "Adap", 3512402, "sit-tib", "Tibt", ancestors = "dz", override_translit = true, -- Tibt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["adq"] = { "Adangbe", 34730, "alv-gda", "Latn", ancestors = "ada", } m["adr"] = { "Adonara", 4684505, "poz-cet", "Latn", } m["ads"] = { "Adamorobe Sign Language", 27709, "sgn", "Latn", -- when documented } m["adt"] = { "Adnyamathanha", 2225391, "aus-psw", "Latn", } m["adu"] = { "Aduge", 34734, "alv-nwd", "Latn", ancestors = "opa", } m["adw"] = { "Amondawa", 12626847, "tup-gua", "Latn", } m["ady"] = { "West Circassian", 27776, "cau-cir", "Cyrl, Latn, Arab", translit = { Cyrl = "cau-cir-translit", Arab = "ar-translit", }, override_translit = true, display_text = { Cyrl = s["cau-Cyrl-displaytext"] }, strip_diacritics = { Cyrl = s["cau-Cyrl-stripdiacritics"], Latn = s["cau-Latn-stripdiacritics"], }, sort_key = { Cyrl = { from = { "кхъу", "къӏу", -- 4 chars "гъу", "джу", "дзу", "жъу", "къу", "кхъ", "къӏ", "кӏу", "кӏь", "лъу", "лӏу", "пӏу", "сӏу", "тӏу", "фӏу", "хъу", "цӏу", "чъу", "чӏу", "шъу", "шӏу", "щӏу", -- 3 chars "гу", "гъ", "гь", "дж", "дз", "ё", "жъ", "жь", "ку", "къ", "кь", "кӏ", "лъ", "ль", "лӏ", "пӏ", "сӏ", "тӏ", "фӏ", "ху", "хъ", "хь", "цу", "цӏ", "чу", "чъ", "чӏ", "шъ", "шӏ", "щӏ", "ӏу", "ӏь" -- 2 chars }, to = { "к" .. p[5], "к" .. p[7], "г" .. p[3], "д" .. p[2], "д" .. p[4], "ж" .. p[2], "к" .. p[3], "к" .. p[4], "к" .. p[6], "к" .. p[10], "к" .. p[11], "л" .. p[2], "л" .. p[5], "п" .. p[2], "с" .. p[2], "т" .. p[2], "ф" .. p[2], "х" .. p[3], "ц" .. p[3], "ч" .. p[3], "ч" .. p[5], "ш" .. p[2], "ш" .. p[4], "щ" .. p[2], "г" .. p[1], "г" .. p[2], "г" .. p[4], "д" .. p[1], "д" .. p[3], "е" .. p[1], "ж" .. p[1], "ж" .. p[3], "к" .. p[1], "к" .. p[2], "к" .. p[8], "к" .. p[9], "л" .. p[1], "л" .. p[3], "л" .. p[4], "п" .. p[1], "с" .. p[1], "т" .. p[1], "ф" .. p[1], "х" .. p[1], "х" .. p[2], "х" .. p[4], "ц" .. p[1], "ц" .. p[2], "ч" .. p[1], "ч" .. p[2], "ч" .. p[4], "ш" .. p[1], "ш" .. p[3], "щ" .. p[1], "ӏ" .. p[1], "ӏ" .. p[2] } }, }, } m["adz"] = { "Adzera", 3327445, "poz-ocw", "Latn", } m["aea"] = { "Areba", 3509129, "aus-pam", "Latn", } m["aeb"] = { "Tunisian Arabic", 56240, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["aed"] = { "Argentine Sign Language", 3322073, "sgn", "Latn", -- when documented } m["aee"] = { "Northeast Pashayi", 12642198, "inc-pas", "Arab, Latn", } m["aek"] = { "Haeke", 5638166, "poz-cln", "Latn", } m["ael"] = { "Ambele", 34818, "nic-grf", "Latn", } m["aem"] = { "Arem", 3507920, "mkh-vie", "Latn", } m["aen"] = { "Armenian Sign Language", 3446604, "sgn", } m["aeq"] = { "Aer", 3246741, "inc-wes", "Arab", } m["aer"] = { "Eastern Arrernte", 10728232, "aus-rnd", "Latn", } m["aes"] = { "Alsea", 2395641, nil, "Latn", } m["aeu"] = { "Akeu", 4700657, "tbq-sil", "Latn", } m["aew"] = { "Ambakich", 56642, "paa-eke", "Latn", } m["aey"] = { "Amele", 3508025, "ngf-gum", "Latn", } m["aez"] = { "Aeka", 16110528, "ngf-oro", "Latn", } m["afb"] = { "Gulf Arabic", 56385, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["afd"] = { "Andai", 4753480, "paa-arf", "Latn", } m["afe"] = { "Putukwam", 3914930, "nic-ben", "Latn", } m["afg"] = { "Afghan Sign Language", 4689093, "sgn", } m["afh"] = { "Afrihili", 384707, "art", "Latn", type = "appendix-constructed", } m["afi"] = { "Akrukay", 57003, "paa-tam", "Latn", } m["afk"] = { "Nanubae", 6964416, "paa-arf", "Latn", } m["afn"] = { "Defaka", 35174, "nic", "Latn", } m["afo"] = { "Eloyi", 3914066, "nic-plt", "Latn", } m["afp"] = { "Tapei", 16887371, "paa-arf", "Latn", } m["afs"] = { "Afro-Seminole Creole", 27867, "crp", "Latn", ancestors = "en", } m["aft"] = { "Afitti", 3400829, "sdv-nyi", "Latn", } m["afu"] = { "Awutu", 34847, "alv-gng", "Latn", } m["afz"] = { "Obokuitai", 7075258, "paa-clp", "Latn", } m["agb"] = { "Legbo", 35584, "nic-uce", "Latn", } m["agc"] = { "Agatu", 34732, "alv-ido", "Latn", } m["agd"] = { "Agarabi", 3399642, "ngf-gau", "Latn", } m["age"] = { "Angal", 10951553, "ngf-ank", "Latn", } m["agf"] = { "Arguni", 12473346, "poz-cet", "Latn", } m["agg"] = { "Angor", 3508100, "paa-sng", "Latn", } m["agh"] = { "Ngelima", 7022266, "bnt-bta", "Latn", } m["agj"] = { "Argobba", 29292, "sem-eth", "Ethi", } m["agk"] = { "Isarog Agta", 6078982, "phi", "Latn", } m["agl"] = { "Fembe", 372927, "ngf-est", "Latn", } m["agm"] = { "Angaataha", 3508001, "ngf-ang", "Latn", } m["agn"] = { "Agutaynen", 3399717, "phi-kal", "Latn", } m["ago"] = { "Tainae", 7676186, "ngf-taa", "Latn", } m["agq"] = { "Aghem", 34737, "nic-rnw", "Latn", } m["agr"] = { "Aguaruna", 1526530, "sai-jiv", "Latn", } m["ags"] = { "Esimbi", 35260, "nic-bds", "Latn", } m["agt"] = { "Central Cagayan Agta", 5017296, "phi", "Latn", } m["agu"] = { "Aguacateca", 35091, "myn", "Latn", } m["agv"] = { "Remontado Agta", 3508085, "phi", "Latn", } m["agw"] = { "Kahua", 3191906, "poz-sls", "Latn", } m["agx"] = { "Aghul", 36498, "cau-esm", "Cyrl", translit = "cau-nec-translit", override_translit = true, display_text = s["cau-Cyrl-displaytext"], strip_diacritics = s["cau-Cyrl-stripdiacritics"], sort_key = { from = {"аь", "гъ", "гь", "гӏ", "дж", "ё", "къ", "кь", "кӏ", "оь", "пӏ", "тӏ", "уь", "хъ", "хь", "хӏ", "цӏ", "чӏ"}, to = {"а" .. p[1], "г" .. p[1], "г" .. p[2], "г" .. p[3], "д" .. p[1], "е" .. p[1], "к" .. p[1], "к" .. p[2], "к" .. p[3], "о" .. p[1], "п" .. p[1], "т" .. p[1], "у" .. p[1], "х" .. p[1], "х" .. p[2], "х" .. p[3], "ц" .. p[1], "ч" .. p[1]} }, } m["agy"] = { "Southern Alta", 7569611, "phi", "Latn", } m["agz"] = { "Mount Iriga Agta", 6921432, "phi", "Latn", } m["aha"] = { "Ahanta", 34729, "alv-ctn", "Latn", } m["ahb"] = { "Axamb", 2874710, "poz-vnc", "Latn", } m["ahg"] = { "Qimant", 35663, "cus-cen", "Latn", } m["ahh"] = { "Aghu", 3436645, "ngf-awy", "Latn", } m["ahi"] = { "Tiagba", 3400073, "kro-aiz", "Latn", } m["ahk"] = { "Akha", 56643, "tbq-han", "Latn, Mymr, Thai", sort_key = { Thai = { from = {"[%pๆ]", "[็-๎]", "([เแโใไ])([ก-ฮ])"}, to = {"", "", "%2%1"} }, }, } m["ahl"] = { "Igo", 35412, "alv-ktg", "Latn", } m["ahm"] = { "Mobu", 35967, "kro-aiz", "Latn", } m["ahn"] = { "Àhàn", 34723, "alv-aah", "Latn", } m["aho"] = { "Ahom", 34778, "tai-swe", "Ahom", translit = "Ahom-translit", } m["ahp"] = { "Apro", 34810, "alv-kwa", "Latn", } m["ahr"] = { "Ahirani", 15549890, "raj", "Deva", translit = "mr-translit", } m["ahs"] = { "Ashe", 34823, "nic-plc", "Latn", } m["aht"] = { "Ahtna", 21058, "ath-nor", "Latn", } m["aia"] = { "Arosi", 2863483, "poz-sls", "Latn", } m["aib"] = { "Äynu", 27927, "qfa-mix", "Arab, Latn", ancestors = "ug, fa" } m["aic"] = { "Ainbai", 3332149, "paa-bew", "Latn", } m["aid"] = { "Alngith", 3279409, "aus-pmn", "Latn", } m["aie"] = { "Amara", 2841180, "poz-ocw", "Latn", } m["aif"] = { "Agi", 3331491, "paa-wpa", "Latn", } m["aig"] = { "Antigua and Barbuda Creole English", 3244184, "crp", "Latn", ancestors = "en", } m["aih"] = { "Ai-Cham", 2827749, "qfa-kms", "Latn, Hani", sort_key = { Hani = "Hani-sortkey" }, } m["aii"] = { "Assyrian Neo-Aramaic", 29440, "sem-nna", "Syrc", translit = "aii-translit", strip_diacritics = "Syrc-stripdiacritics", } m["aij"] = { "Lishanid Noshan", 3436467, "sem-nna", "Hebr", -- Hebr display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["aik"] = { "Ake", 34808, "nic-pls", "Latn", } m["ail"] = { "Aimele", 3327418, "ngf-bos", "Latn", } m["aim"] = { "Aimol", 4697175, "tbq-kuk", "Latn, Beng", } m["ain"] = { "Ainu", 27969, "qfa-ain", "Kana, Latn, Cyrl", sort_key = { Kana = "Kana-sortkey" }, } m["aio"] = { "Aiton", 3399725, "tai-swe", "Mymr", translit = "aio-phk-translit", display_text = s["aio-displaytext"], strip_diacritics = s["aio-stripdiacritics"], } m["aip"] = { "Burumakok", 5000984, "ngf-wok", "Latn", } m["air"] = { "Airoran", 3321131, "paa-saa", "Latn", } m["ait"] = { "Arikem", 3446679, "tup", "Latn", } m["aiw"] = { "Aari", 7495, "omv-aro", "Latn", } m["aix"] = { "Aighon", 3504287, "poz-ocw", "Latn", } m["aiy"] = { "Ali", 34814, "gba-eas", "Latn", } m["aja"] = { "Aja (East Africa)", 3237491, "csu-bkr", "Latn", } m["ajg"] = { "Aja (West Africa)", 35035, "alv-gbe", "Latn", } m["aji"] = { "Ajië", 2828867, "poz-cln", "Latn", } m["ajn"] = { "Andajin", 16111302, "aus-wor", "Latn", } m["ajp"] = { "South Levantine Arabic", 55633582, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["ajs"] = { "Algerian Jewish Sign Language", 5555709, "sgn", } -- m["ajt"] Judeo-Tunisian Arabic now included in Tunisian Arabic -- m["aju"] Judeo-Moroccan Arabic now included in Moroccan Arabic m["ajw"] = { "Ajawa", 56645, "cdc-wst", "Latn", } m["ajz"] = { "Amri Karbi", 3508092, "tbq-kuk", "Latn", ancestors = "mjw", } m["akb"] = { "Angkola Batak", 2640686, "btk", "Latn, Batk", } m["akc"] = { "Mpur", 3327139, "qfa-iso", -- Papuan; based on Palmer (2018), Ethnologue and Glottolog "Latn", } m["akd"] = { "Ukpet-Ehom", 36618, "nic-ucr", "Latn", } m["ake"] = { "Akawaio", 28059, "sai-pem", "Latn", } m["akf"] = { "Akpa", 34801, "alv-ido", "Latn", } m["akg"] = { "Anakalangu", 4750964, "poz-cet", "Latn", } m["akh"] = { "Angal Heneng", 10950354, "ngf-ank", "Latn", } m["aki"] = { "Aiome", 56735, "paa-aia", "Latn", } m["akj"] = { "Jeru", 2919121, "qfa-adn", "Latn, Deva", } m["akk"] = { "Akkadian", 35518, "sem-eas", "Xsux, Latn", } m["akl"] = { "Aklanon", 8773, "phi", "Latn", } m["akm"] = { "Aka-Bo", 35361, "qfa-adn", "Latn", } m["ako"] = { "Akurio", 56650, "sai-tar", "Latn", } m["akp"] = { "Siwu", 36470, "alv-ntg", "Latn", } m["akq"] = { "Ak", 56654, "paa-sep", "Latn", } m["akr"] = { "Araki", 2699882, "poz-vnn", "Latn", } m["aks"] = { "Akaselem", 34817, "nic-grm", "Latn", } m["akt"] = { "Akolet", 3330162, "poz-ocw", "Latn", } m["aku"] = { "Akum", 34799, "nic-ykb", "Latn", } m["akv"] = { "Akhvakh", 56423, "cau-and", "Cyrl", translit = "cau-nec-translit", override_translit = true, display_text = s["cau-Cyrl-displaytext"], strip_diacritics = s["cau-Cyrl-stripdiacritics"], } m["akw"] = { "Akwa", 34802, "bnt-mbo", "Latn", } m["akx"] = { "Aka-Kede", 3436816, "qfa-adc", "Latn", } m["aky"] = { "Aka-Kol", 3436784, "qfa-adc", "Latn", } m["akz"] = { "Alabama", 1815020, "nai-mus", "Latn", } m["ala"] = { "Alago", 34813, "alv-ido", "Latn", } m["alc"] = { "Kawésqar", 56544, "aqa", "Latn", } m["ald"] = { "Alladian", 34837, "alv-lag", "Latn", } m["ale"] = { "Aleut", 27210, "esx", "Latn, Cyrl", } m["alf"] = { "Alege", 34815, "nic-ben", "Latn", } m["alh"] = { "Alawa", 2147917, "aus-gun", "Latn", } m["ali"] = { "Amaimon", 3327427, "ngf-mad", "Latn", } m["alj"] = { "Alangan", 3327423, "phi", "Latn", } m["alk"] = { "Alak", 2714690, "mkh", "Latn", } m["all"] = { "Allar", 3393634, "dra-mal", "Mlym", -- Mlym translit in [[Module:scripts/data]] (NOTE: not present before, presumably an accidental omission) } -- "aln" is treated as "sq", see [[WT:LT]] m["alm"] = { "Amblong", 11022615, "poz-vnn", "Latn", } m["alo"] = { "Larike-Wakasihu", 3217929, "poz-cma", "Latn", } m["alp"] = { "Alune", 3327367, "poz-cet", "Latn", } m["alq"] = { "Algonquin", 28092, "alg", "Latn, Cans", ancestors = "oj", } m["alr"] = { "Alutor", 28213, "qfa-ckn", "Cyrl", strip_diacritics = { from = {"['’]"}, to = {"ʼ"} }, sort_key = { from = {"вʼ", "гʼ", "ғ", "ә", "ё", "ӄ", "ӈ"}, to = {"в" .. p[1], "г" .. p[1], "г" .. p[2], "е" .. p[1], "е" .. p[2], "к" .. p[1], "н" .. p[1]} }, } m["alt"] = { "Southern Altai", 1991779, "trk-kkp", "Cyrl", translit = "Altai-translit", sort_key = { from = {"ј", "ё", "ҥ", "ӧ", "ӱ"}, to = {"д" .. p[1], "е" .. p[1], "н" .. p[1], "о" .. p[1], "у" .. p[1]} }, } m["alu"] = { "'Are'are", 5160, "poz-sls", "Latn", } m["alw"] = { "Alaba", 56652, "cus-hec", "Latn", } m["alx"] = { "Amol", 3504260, "paa-pal", "Latn", } m["aly"] = { "Alyawarr", 3327389, "aus-rnd", "Latn", } m["alz"] = { "Alur", 56507, "sdv-los", "Latn", } m["ama"] = { "Amanayé", 3508053, "tup-gua", "Latn", } m["amb"] = { "Ambo", 3450142, "nic-tvn", "Latn", } m["amc"] = { "Amahuaca", 2669150, "sai-pan", "Latn", } m["ame"] = { "Yanesha'", 3088540, "awd", "Latn", } m["amf"] = { "Hamer-Banna", 35764, "omv-aro", "Latn, Ethi", sort_key = "amf-utilities" } m["amg"] = { "Amurdag", 3360016, "aus-wdj", "Latn", } m["ami"] = { "Amis", 35132, "map", "Latn", } m["amj"] = { "Amdang", 28335, "ssa-fur", "Latn", } m["amk"] = { "Ambai", 1875885, "poz-hce", "Latn", } m["aml"] = { "War-Jaintia", 56321, "aav-khs", "Latn", } m["amm"] = { "Ama", 3446626, "paa-lma", "Latn", } m["amn"] = { "Amanab", 3327399, "paa-war", "Latn", } m["amo"] = { "Amo", 34826, "nic-kne", "Latn", } m["amp"] = { "Alamblak", 56688, "paa-sep", "Latn", } m["amq"] = { "Amahai", 3327384, "poz-cma", "Latn", } m["amr"] = { "Amarakaeri", 35128, "sai-har", "Latn", } m["ams"] = { "Southern Amami Ōshima", 2840986, "jpx-nry", "Jpan", translit = s["jpx-translit"], display_text = s["jpx-displaytext"], strip_diacritics = s["jpx-stripdiacritics"], sort_key = s["jpx-sortkey"], } m["amt"] = { "Amto", 56517, "paa-amu", "Latn", } m["amu"] = { "Guerrero Amuzgo", 3501942, "omq", "Latn", } m["amv"] = { "Ambelau", 2669214, "poz-cma", "Latn", } m["amw"] = { "Western Neo-Aramaic", 34226, "sem-arw", "Armi, Syrc, Latn", strip_diacritics = { Syrc = "Syrc-stripdiacritics" }, } m["amx"] = { "Anmatyerre", 10412317, "aus-rnd", "Latn", } m["amy"] = { "Ami", 10408315, "aus-dal", "Latn", } m["amz"] = { "Atampaya", 3446651, "aus-pam", "Latn", } m["ana"] = { "Andaqui", 2846078, nil, "Latn", } m["anb"] = { "Andoa", 2846171, "sai-zap", "Latn", } m["anc"] = { "Ngas", 35999, "cdc-wst", "Latn", } m["and"] = { "Ansus", 3513300, "poz-hce", "Latn", } m["ane"] = { "Xârâcùù", 3571097, "poz-cln", "Latn", } m["anf"] = { "Animere", 34783, "alv-ktg", "Latn", } m["ang"] = { "Old English", 42365, "gmw-ang", "Latn, Runr", translit = { Runr = "Runr-translit" }, strip_diacritics = { Latn = { remove_diacritics = c.acute .. c.circ .. c.macron .. c.breve .. c.dotabove .. c.diaer .. c.dotbelow, from = {"[Ƿƿ]"}, to = {{ ["Ƿ"] = "W", ["ƿ"] = "w", }}, }, }, sort_key = { Latn = { remove_diacritics = c.acute .. c.circ .. c.macron .. c.breve .. c.dotabove .. c.diaer .. c.dotbelow, from = {"[æƀꝺðꝼᵹȝłœꞃꞅꞇþꝥꝧƿ]"}, to = {{ ["æ"] = "ae", ["ƀ"] = "b", ["ꝺ"] = "d", ["ð"] = "d" .. p[1], ["ꝼ"] = "f", ["ᵹ"] = "g", ["ȝ"] = "g" .. p[1], ["ł"] = "l", ["œ"] = "oe", ["ꞃ"] = "r", ["ꞅ"] = "s", ["ꞇ"] = "t", ["þ"] = "t" .. p[1], ["ꝥ"] = "t" .. p[1], ["ꝧ"] = "t" .. p[1], ["ƿ"] = "w", }}, }, }, standard_chars = { Latn = "AaÆæBbCcDdÐðEeFfGgHhIiLlMmNnOoŒœPpRrSsTtÞþUuWwXxYy", c.punc, }, } m["anh"] = { "Nend", 6991554, "ngf-wso", "Latn", } m["ani"] = { "Andi", 34849, "cau-and", "Cyrl", translit = "cau-nec-translit", override_translit = true, display_text = s["cau-Cyrl-displaytext"], strip_diacritics = s["cau-Cyrl-stripdiacritics"], } m["anj"] = { "Anor", 56458, "paa-aia", "Latn", } m["ank"] = { "Goemai", 35272, "cdc-wst", "Latn", } m["anl"] = { "Anu", 4777679, "sit-mru", "Latn", } m["anm"] = { "Anāl", 56235, "tbq-kuk", "Latn", } m["ann"] = { "Obolo", 36614, "nic-lcr", "Latn", } m["ano"] = { "Andoque", 2669225, "qfa-iso", "Latn", } m["anp"] = { "Angika", 28378, "inc-bih", "Deva, Kthi", translit = { Deva = "hi-translit", Kthi = "bho-Kthi-translit", }, } m["anq"] = { "Jarawa", 2475526, "qfa-ong", "Latn", } m["anr"] = { "Andh", 4754314, "inc-sou", "Deva", } m["ans"] = { "Anserma", 3446613, "sai-chc", "Latn", } m["ant"] = { "Antakarinya", 921304, "aus-psw", "Latn", } m["anu"] = { "Anuak", 56677, "sdv-lon", "Latn", } m["anv"] = { "Denya", 35187, "nic-mam", "Latn", } m["anw"] = { "Anaang", 2845320, "nic-ief", "Latn", } m["anx"] = { "Andra-Hus", 2846195, "poz-aay", "Latn", } m["any"] = { "Anyi", 28395, "alv-ctn", "Latn", } m["anz"] = { "Anem", 56512, "qfa-dis", -- Papuan; might be an isolate or in a putative West New Britain family "Latn", } m["aoa"] = { "Angolar", 34994, "crp", "Latn", ancestors = "pt", } m["aob"] = { "Abom", 3446647, "qfa-dis", -- Papuan; possibly a divergent Tirio language (Anim family), or a top-level TNG node "Latn", } m["aoc"] = { "Pemon", 10729616, "sai-pem", "Latn", } m["aod"] = { "Andarum", 3507888, "paa-ata", "Latn", } m["aoe"] = { "Angal Enen", 10951638, "ngf-ank", "Latn", } m["aof"] = { "Bragat", 3507977, "paa-pal", "Latn", } m["aog"] = { "Angoram", 56366, -- cf 6754745 for merged dialect "paa-lse", "Latn", } m["aoi"] = { "Anindilyakwa", 2714654, "aus-arn", "Latn", } m["aoj"] = { "Mufian", 3507881, "paa-ara", "Latn", } m["aok"] = { "Arhö", 4790086, "poz-cln", "Latn", } m["aol"] = { "Alorese", 3332062, "poz", "Latn", } m["aom"] = { "Ömie", 8078975, "ngf-koi", "Latn", } m["aon"] = { "Bumbita Arapesh", 3508044, "paa-ara", "Latn", } m["aor"] = { "Aore", 12627129, "poz-vnn", "Latn", } m["aos"] = { "Taikat", 7676018, "paa-taa", "Latn", } m["aot"] = { "Atong (India)", 5646, "tbq-bdg", "Latn, Beng", } m["aou"] = { "A'ou", 16109994, "gio", "Latn", -- also Hani? } m["aox"] = { "Atorada", 3507932, "awd", "Latn", } m["aoz"] = { "Uab Meto", 3441962, "poz-tim", "Latn", } m["apb"] = { "Sa'a", 36294, "poz-sls", "Latn", } m["apc"] = { "North Levantine Arabic", 22809485, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["apd"] = { "Sudanese Arabic", 56573, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["ape"] = { "Bukiyip", 3507895, "paa-ara", "Latn", } m["apf"] = { "Pahanan Agta", 7135432, "phi", "Latn", } m["apg"] = { "Ampanang", 4748035, "poz", "Latn", } m["aph"] = { "Athpare", 3449126, "sit-kie", "Deva, Latn", } m["api"] = { "Apiaká", 3507941, "tup-gua", "Latn", } m["apj"] = { "Jicarilla", 28277, "apa", "Latn", } m["apk"] = { "Plains Apache", 27861, "apa", "Latn", } m["apl"] = { "Lipan", 28269, "apa", "Latn", } m["apm"] = { "Chiricahua", 13368, "apa", "Latn", } m["apn"] = { "Apinayé", 2858311, "sai-nje", "Latn", } m["apo"] = { "Ambul", 12627135, "poz-ocw", "Latn", } m["app"] = { "Apma", 2669188, "poz-vnn", "Latn", } m["apq"] = { "A-Pucikwar", 28466, "qfa-adc", "Latn", } m["apr"] = { "Arop-Lokep", 2863482, "poz-ocw", "Latn", } m["aps"] = { "Arop-Sissano", 12627242, "poz-ocw", "Latn", } m["apt"] = { "Apatani", 56306, "sit-tan", "Latn", } m["apu"] = { "Apurinã", 2859081, "awd", "Latn", } m["apv"] = { "Alapmunte", 16110782, "sai-nmk", "Latn", } m["apw"] = { "Western Apache", 28060, "apa", "Latn", } m["apx"] = { "Aputai", 12473343, "poz-tim", "Latn", } m["apy"] = { "Apalaí", 2736980, "sai-gui", "Latn", } m["apz"] = { "Safeyoka", 7398693, "ngf-woj", "Latn", } m["aqc"] = { "Archi", 34915, "cau-lzg", "Cyrl", translit = "cau-nec-translit", override_translit = true, display_text = s["cau-Cyrl-displaytext"], strip_diacritics = s["cau-Cyrl-stripdiacritics"], sort_key = { from = { "ккъӏв", "ххьӏв", -- 5 chars "гъӏв", "ёоӏ", "ккъӏ", "ккъв", "къӏв", "ллъв", "ххьӏ", "хъӏв", "хьӏв", "ццӏв", "ччӏв", -- 4 chars "ааӏ", "гӏв", "гъӏ", "гъв", "гьв", "ееӏ", "ёӏ", "ёо", "ииӏ", "кӏв", "ккв", "ккъ", "къӏ", "къв", "кьв", "лӏв", "ллъ", "лъв", "льв", "ооӏ", "пӏв", "ппв", "ссв", "тӏв", "ттв", "ууӏ", "хӏв", "ххв", "хъӏ", "хъв", "хьӏ", "цӏв", "ццӏ", "ццв", "чӏв", "ччӏ", "ээӏ", "юуӏ", "яаӏ", -- 3 chars "аӏ", "аа", "гӏ", "гв", "гъ", "гь", "дв", "еӏ", "ее", "ё", "жв", "зв", "иӏ", "ии", "кӏ", "кв", "кк", "къ", "кь", "лӏ", "лв", "лъ", "ль", "оӏ", "оо", "пӏ", "пв", "пп", "св", "сс", "тӏ", "тв", "тт", "уӏ", "уу", "фв", "хӏ", "хв", "хх", "хъ", "цӏ", "цв", "цц", "чӏ", "чв", "шв", "щв", "эӏ", "ээ", "юӏ", "юу", "яӏ", "яа" -- 2 chars }, to = { "к" .. p[8], "х" .. p[7], "г" .. p[6], "е" .. p[7], "к" .. p[7], "к" .. p[9], "к" .. p[12], "л" .. p[5], "х" .. p[6], "х" .. p[10], "х" .. p[13], "ц" .. p[6], "ч" .. p[5], "а" .. p[3], "г" .. p[2], "г" .. p[5], "г" .. p[7], "г" .. p[9], "е" .. p[3], "е" .. p[5], "е" .. p[6], "и" .. p[3], "к" .. p[2], "к" .. p[5], "к" .. p[6], "к" .. p[11], "к" .. p[13], "к" .. p[15], "л" .. p[2], "л" .. p[4], "л" .. p[7], "л" .. p[9], "о" .. p[3], "п" .. p[2], "п" .. p[5], "с" .. p[3], "т" .. p[2], "т" .. p[5], "у" .. p[3], "х" .. p[2], "х" .. p[5], "х" .. p[9], "х" .. p[11], "х" .. p[12], "ц" .. p[2], "ц" .. p[5], "ц" .. p[7], "ч" .. p[2], "ч" .. p[4], "э" .. p[3], "ю" .. p[3], "я" .. p[3], "а" .. p[1], "а" .. p[2], "г" .. p[1], "г" .. p[3], "г" .. p[4], "г" .. p[8], "д" .. p[1], "е" .. p[1], "е" .. p[2], "е" .. p[4], "ж" .. p[1], "з" .. p[1], "и" .. p[1], "и" .. p[2], "к" .. p[1], "к" .. p[3], "к" .. p[4], "к" .. p[10], "к" .. p[14], "л" .. p[1], "л" .. p[3], "л" .. p[6], "л" .. p[8], "о" .. p[1], "о" .. p[2], "п" .. p[1], "п" .. p[3], "п" .. p[4], "с" .. p[1], "с" .. p[2], "т" .. p[1], "т" .. p[3], "т" .. p[4], "у" .. p[1], "у" .. p[2], "ф" .. p[1], "х" .. p[1], "х" .. p[3], "х" .. p[4], "х" .. p[8], "ц" .. p[1], "ц" .. p[3], "ц" .. p[4], "ч" .. p[1], "ч" .. p[3], "ш" .. p[1], "щ" .. p[1], "э" .. p[1], "э" .. p[2], "ю" .. p[1], "ю" .. p[2], "я" .. p[1], "я" .. p[2] } }, } m["aqd"] = { "Ampari Dogon", 4748057, "nic-dgw", "Latn", } m["aqg"] = { "Arigidi", 34829, "alv-von", "Latn", } m["aqm"] = { "Atohwaim", 11732297, "paa-kay", "Latn", } m["aqn"] = { "Northern Alta", 7058116, "phi", "Latn", } m["aqp"] = { "Atakapa", 10975683, "qfa-iso", "Latn", } m["aqr"] = { "Arhâ", 4790085, "poz-cln", "Latn", } m["aqt"] = { "Angaité", 15736037, "sai-mas", "Latn", } m["aqz"] = { "Akuntsu", 4701960, "tup", "Latn", } m["arc"] = { "Aramaic", 28602, "sem-ara", "Hebr, Armi, Syrc, Palm, Nbat, Phnx, Mand, Samr, Hatr, Elym", translit = { Armi = "Armi-translit", Palm = "Palm-translit", }, strip_diacritics = { -- The first three were added by [[User:Wikitiki89]] in 2015 for use with Syriac, which has diacritics that look -- like a diaeresis (syāmē) and macrons above and below (mṭalqānā); see Wikipedia [[w:Syriac alphabet]]. But -- I don't know if they are actually represented using these diacritics. Syrc = {remove_diacritics = c.macron .. c.diaer .. c.macronbelow .. u(0x0730) .. "-" .. u(0x0748)}, }, -- Hebr display_text, strip_diacritics, sort_key in [[Module:scripts/data]] -- Samr strip_diacritics, sort_key in [[Module:scripts/data]]; previously no sort_key for Samr, presumably a mistake -- Phnx translit in [[Module:scripts/data]] (NOTE: not present before, presumably an accidental omission) } m["ard"] = { "Arabana", 3507959, "aus-kar", "Latn", } m["are"] = { "Western Arrernte", 12645549, "aus-rnd", "Latn", } m["arh"] = { "Arhuaco", 2640621, "cba", "Latn", } m["ari"] = { "Arikara", 56539, "cdd", "Latn", strip_diacritics = {remove_diacritics = c.acute}, } m["arj"] = { "Arapaso", 9627356, "sai-tuc", "Latn", } m["ark"] = { "Arikapú", 3446640, "sai-mje", "Latn", } m["arl"] = { "Arabela", 2591221, "sai-zap", "Latn", } m["arn"] = { "Mapudungun", 33730, "sai-ara", "Latn", } m["aro"] = { "Araona", 958414, "sai-tac", "Latn", } m["arp"] = { "Arapaho", 56417, "alg-ara", "Latn", } m["arq"] = { "Algerian Arabic", 56499, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["arr"] = { "Arara-Karo", 35539, "tup", "Latn", } m["ars"] = { "Najdi Arabic", 56574, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["aru"] = { "Arua", 2746221, "auf", "Latn", } m["arv"] = { "Arbore", 56883, "cus-eas", "Latn", } m["arw"] = { "Lokono", 2655664, "awd-taa", "Latn", } m["arx"] = { "Aruá", 3507907, "tup", "Latn", } m["ary"] = { "Moroccan Arabic", 56426, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["arz"] = { "Egyptian Arabic", 29919, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["asa"] = { "Pare", 36403, "bnt-par", "Latn", } m["asb"] = { "Assiniboine", 2591288, "sio-dkt", "Latn", } m["asc"] = { "Casuarina Coast Asmat", 11732046, "ngf-asm", "Latn", } m["ase"] = { "American Sign Language", 14759, "sgn-asl", "Sgnw", } m["asf"] = { "Auslan", 29525, "sgn", "Latn", -- when documented } m["asg"] = { "Cishingini", 35199, "nic-kam", "Latn", } m["ash"] = { "Abishira", 2871740, "qfa-dis", -- extinct, poorly documented; isolate or in a proposed Tequiraca-Canichana family by Kaufman (1994) "Latn", } m["asi"] = { "Buruwai", 5001031, "ngf-sab", "Latn", } m["asj"] = { "Nsari", 36418, "nic-bbe", "Latn", } m["ask"] = { "Ashkun", 29379, "nur-sou", "Arab, Latn", } m["asl"] = { "Asilulu", 12473347, "poz-cma", "Latn", } m["asn"] = { "Xingú Asuriní", 8044571, "tup-gua", "Latn", } m["aso"] = { "Dano", 5220979, "ngf-gah", "Latn", } m["asp"] = { "Algerian Sign Language", 3135421, "sgn", } m["asq"] = { "Austrian Sign Language", 36668, "sgn", "Latn", -- when documented } m["asr"] = { "Asuri", 3504321, "mun", "Latn", -- when documented } m["ass"] = { "Ipulo", 35408, "nic-tvc", "Latn", } m["ast"] = { "Asturian", 29507, "roa-asl", "Latn", } m["asu"] = { "Tocantins Asurini", 32041490, "tup-gua", "Latn", } m["asv"] = { "Asoa", 56296, "csu-maa", "Latn", } m["asw"] = { "Australian Aboriginal Sign Language", 955216, "sgn", "Latn", -- when documented } m["asx"] = { "Muratayak", 11732766, "ngf-war", "Latn", } m["asy"] = { "Yaosakor Asmat", 16113158, "ngf-asm", "Latn", } m["asz"] = { "As", 2866218, "poz-hce", "Latn", } m["ata"] = { "Pele-Ata", 56511, "qfa-dis", -- Papuan; possibly in a putative West New Britain family, or an isolate "Latn", } m["atb"] = { "Zaiwa", 56594, "tbq-brm", "Latn, Lisu", -- also Hani? -- Lisu translit, sort_key in [[Module:scripts/data]] } m["atc"] = { "Atsahuaca", 4817730, "sai-pan", "Latn", } m["atd"] = { "Ata Manobo", 12627315, "mno", "Latn", } m["ate"] = { "Atemble", 4813055, "ngf-wso", "Latn", } m["atg"] = { "Okpela", 7082551, "alv-yek", "Latn", } m["ati"] = { "Attié", 34844, "alv-lag", "Latn", } m["atj"] = { "Atikamekw", 56590, "alg", "Latn", ancestors = "cr", } m["atk"] = { "Ati", 3217458, "phi", "Latn", } m["atl"] = { "Mount Iraya Agta", 6921430, "phi", "Latn", } m["atm"] = { "Ata", 4812603, "phi", "Latn", } m["ato"] = { "Atong (Cameroon)", 34824, "nic-grs", "Latn", } m["atp"] = { "Pudtol Atta", 12640726, "phi", "Latn", } m["atq"] = { "Aralle-Tabulahan", 4783889, "poz-ssw", "Latn", } m["atr"] = { "Waimiri-Atroari", 56865, "sai-car", "Latn", } m["ats"] = { "Gros Ventre", 56628, "alg-ara", "Latn", } m["att"] = { "Pamplona Atta", 12639245, "phi", "Latn", } m["atu"] = { "Reel", 7306882, "sdv-dnu", "Latn", } m["atv"] = { "Northern Altai", 2640863, "trk-ssb", "Cyrl", translit = "Altai-translit", } m["atw"] = { "Atsugewi", 56718, "nai-pal", "Latn", } m["atx"] = { "Arutani", 56609, nil, "Latn", } m["aty"] = { "Aneityum", 2379113, "poz-vns", "Latn", } m["atz"] = { "Arta", 3508067, "phi", "Latn", } m["aua"] = { "Asumboa", 4811870, "poz-tem", "Latn", } m["aub"] = { "Alugu", 12626798, "tbq-urp", "Latn", -- also Hani? } m["auc"] = { "Huaorani", 758570, "qfa-iso", "Latn", } m["aud"] = { "Anuta", 35326, "poz-pnp", "Latn", } m["aug"] = { "Aguna", 34733, "alv-gbe", "Latn", } m["auh"] = { "Aushi", 2872082, "bnt-sbi", "Latn", } m["aui"] = { "Anuki", 3508132, "poz-ocw", "Latn", } m["auj"] = { "Awjila", 56398, "ber", "Latn, Arab, Tfng", } m["auk"] = { "Heyo", 3504295, "paa-hya", "Latn", } m["aul"] = { "Aulua", 427300, "poz-vnc", "Latn", } m["aum"] = { "Asu", 34798, "alv-ngb", "Latn", } m["aun"] = { "Molmo One", 12637224, "paa-trr", "Latn", } m["auo"] = { "Auyokawa", 56247, "cdc-wst", "Latn", } m["aup"] = { "Makayam", 6738863, "paa-tir", "Latn", } m["auq"] = { "Anus", 23855, "poz-ocw", "Latn", } m["aur"] = { "Aruek", 3504279, "paa-kom", "Latn", } m["aut"] = { "Austral", 2669261, "poz-pep", "Latn", } m["auu"] = { "Auye", 4827334, "ngf-pan", "Latn", } m["auw"] = { "Awyi", 3513326, "paa-taa", "Latn", } m["aux"] = { "Aurá", 3507995, "tup-gua", "Latn", } m["auy"] = { "Auyana", 2873211, "ngf-gau", "Latn", } m["auz"] = { "Uzbeki Arabic", 3399507, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["avb"] = { "Avau", 12627412, "poz-ocw", "Latn", } m["avd"] = { "Alviri-Vidari", 3327357, "xme", "Arab", ancestors = "xme-mid", } m["avi"] = { "Avikam", 34840, "alv-lag", "Latn", } m["avk"] = { "Kotava", 1377116, "art", "Latn", type = "appendix-constructed", } m["avm"] = { "Angkamuthi", 62603022, "aus-pmn", "Latn", } m["avn"] = { "Avatime", 34796, "alv-ktg", "Latn", } -- avo Agavotaguerra retired m["avs"] = { "Aushiri", 3409318, "sai-zap", "Latn", } m["avt"] = { "Au", 3446608, "paa-wap", "Latn", } m["avu"] = { "Avokaya", 56685, "csu-mma", "Latn", } m["avv"] = { "Avá-Canoeiro", 4829584, "tup-gua", "Latn", } m["awa"] = { "Awadhi", 29579, "inc-hie", "Deva, Kthi, Aran", translit = { Deva = "hi-translit" }, } m["awb"] = { "Awa (New Guinea)", 2874650, "ngf-gau", "Latn", } m["awc"] = { "Cicipu", 35193, "nic-kam", "Latn", } m["awe"] = { "Awetí", 4830038, "tup", "Latn", } m["awg"] = { "Anguthimri", 4764288, "aus-pam", "Latn", } m["awh"] = { "Awbono", 3446684, "paa-baa", "Latn", } m["awi"] = { "Aekyom", 3399691, "paa-kae", "Latn", } m["awk"] = { "Awabakal", 3449138, "aus-pam", "Latn", } m["awm"] = { "Arawum", 4784537, "ngf-rai", "Latn", } m["awn"] = { "Awngi", 34934, "cus-cen", "Ethi", } m["awo"] = { "Awak", 3446643, "alv-wjk", "Latn", } m["awr"] = { "Awera", 56379, "paa-flp", "Latn", } m["aws"] = { "South Awyu", 12633986, "ngf-awy", "Latn", } m["awt"] = { "Araweté", 4784535, "tup-gua", "Latn", } m["awu"] = { "Central Awyu", 12628801, "ngf-awy", "Latn", } m["awv"] = { "Jair Awyu", 16110177, "ngf-awy", "Latn", } m["aww"] = { "Awun", 56369, "paa-sep", "Latn", } m["awx"] = { "Awara", 2874670, "ngf-waa", "Latn", } m["awy"] = { "Edera Awyu", 12630425, "ngf-awy", "Latn", } m["axb"] = { "Abipón", 11252539, "sai-guc", "Latn", } m["axe"] = { "Ayerrerenge", 16112737, "aus-pam", "Latn", } m["axg"] = { "Arára (Mato Grosso)", 3446660, nil, "Latn", } m["axk"] = { "Aka (Central Africa)", 11010149, "bnt-ngn", "Latn", } m["axl"] = { "Lower Southern Aranda", 6693295, "aus-rnd", "Latn", } m["axm"] = { "Middle Armenian", 4438498, "hyx", "Armn", ancestors = "xcl", -- Armn translit in [[Module:scripts/data]] override_translit = true, strip_diacritics = { remove_diacritics = "՞՜՛՟", from = {"եւ", "ՙ", "՚"}, to = {"և", "ʻ", "’"} } } m["axx"] = { "Xârâgurè", 8045635, "poz-cln", "Latn", } m["aya"] = { "Awar", 56876, "paa-baw", "Latn", } m["ayb"] = { "Ayizo", 34841, "alv-pph", "Latn", } m["ayd"] = { "Ayabadhu", 3509164, "aus-pmn", "Latn", } m["aye"] = { "Ayere", 34788, "alv-aah", "Latn", } m["ayg"] = { "Nyanga (Togo)", 35446, "alv-gng", "Latn", } m["ayi"] = { "Leyigha", 3914492, "nic-uce", "Latn", } m["ayk"] = { "Akuku", 3450179, "alv-nwd", "Latn", } m["ayl"] = { "Libyan Arabic", 56503, "sem-arb", "Arab", strip_diacritics = "ar-stripdiacritics", } m["ayn"] = { "Yemeni Arabic", 1686766, "sem-arb", "Arab, Hebr", strip_diacritics = { Arab = "ar-stripdiacritics", }, -- Hebr display_text, strip_diacritics, sort_key in [[Module:scripts/data]] } m["ayo"] = { "Ayoreo", 56634, "sai-zam", "Latn", } m["ayp"] = { "North Mesopotamian Arabic", 56577, "sem-arb", "Arab", ancestors = "acm", strip_diacritics = "ar-stripdiacritics", } m["ayq"] = { "Ayi", 56449, "paa-sep", "Latn", } m["ays"] = { "Sorsogon Ayta", 7563752, "phi", "Latn", } m["ayt"] = { "Bataan Ayta", 4921648, "phi", "Latn", } m["ayu"] = { "Ayu", 34786, "alv", "Latn", } -- ayy deleted and removed from ISO; per the removal request, "no linguistic data exists for any [Ayta] language that the -- ancestors of this group might have once spoken. And thus, there is no evidence that this group ever had a language -- distinct from any other Philippine language." [Lobel] m["ayz"] = { "Maybrat", 4830892, "paa-may", -- either an isolate; grouped with Abun and the West Bird's Head family; or in the putative West Papuan family "Latn", } m["aza"] = { "Azha", 4832486, "tbq-axi", "Latn", } m["azd"] = { "Eastern Durango Nahuatl", 16115449, "azc-dur", "Latn", } m["azg"] = { "San Pedro Amuzgos Amuzgo", 35092, "omq", "Latn", } m["azm"] = { "Ipalapa Amuzgo", 12633013, "omq", "Latn", } m["azn"] = { "Western Durango Nahuatl", 12645553, "azc-dur", "Latn", } m["azo"] = { "Awing", 34856, "nic-nge", "Latn", } m["azt"] = { "Faire Atta", 12630884, "phi", "Latn", } m["azz"] = { "Highland Puebla Nahuatl", 12953754, "azc-nah", "Latn", } return require("Module:tungumál").finalizeData(m, "language") 7hpz5al593u05iei23ps5189w82i31e Notandi:Ow! That Hurts!/íslenskuð undirforrit 2 119306 393960 2026-10-09T06:53:12Z Ow! That Hurts! 8952 Ow! That Hurts! færði [[Notandi:Ow! That Hurts!/íslenskuð undirforrit]] á [[Notandi:Ow! That Hurts!/íslenskaðar einingar]] 393960 wikitext text/x-wiki #tilvísun [[Notandi:Ow! That Hurts!/íslenskaðar einingar]] 6nmh935vayoic7c9dn1ckn4emz1vg4l