local export = {}
local require_when_needed = require("Module:require when needed")
local add_suffix -- Defined below.
local find = string.find
local match = string.match
local reverse = string.reverse
local sub = string.sub
local toNFD = mw.ustring.toNFD
local ugsub = require_when_needed("Module:string utilities", "gsub")
local ulower = require_when_needed("Module:string utilities", "lower")
local umatch = require_when_needed("Module:string utilities", "match")
local vowels, diacritics = "aæᴀᴁɐɑɒ@eᴇǝⱻəɛɘɜɞɤiıɪɨᵻoøœᴏɶɔᴐɵuᴜʉᵾɯꟺʊʋʌyʏ"
-- Normalize a string, so that case and diacritics are ignored.
local function normalize(str)
if not diacritics then
diacritics = mw.loadData("Module:headword/data").page.comb_chars.diacritics_all .. "+"
end
return ulower(ugsub(toNFD(str), diacritics, ""))
end
local function default_epenthetic_e(stem)
return sub(stem, -1) ~= "e"
end
local suffixes = {}
-- FIXME, a subrule of rule #1 below says the -ies ending doesn't
-- apply to proper nouns, hence "the Gettys", "the public Ivys".
-- We should maybe consider applying this rule here; but it may not
-- be important as this function is almost always called on common nouns
-- (e.g. parts of speech, place types).
suffixes[""] = { --Fungsi untuk menambah -s pada akhir kata, sudah dinyahaktifkan
final_y_is_i = true,
epenthetic_e = function(stem, term)
if stem ~= term then
return true
end
local ch = sub(stem, -1)
if ch == "s" or ch == "x" or ch == "z" then
return true
elseif ch ~= "h" then
return false
end
ch = sub(stem, -2, -2)
return ch == "c" or ch == "s" or ch == "z"
end,
}
suffixes["ing"] = {
final_consonant_is_doubled = true,
remove_silent_e = true,
}
suffixes["d"] = {
final_y_is_i = true,
final_consonant_is_doubled = true,
epenthetic_e = default_epenthetic_e,
}
suffixes["dst"] = suffixes["d"]
suffixes["st.verb"] = suffixes["d"]
suffixes["th"] = suffixes["d"]
suffixes["n"] = {
final_y_is_i = true,
final_consonant_is_doubled = true,
epenthetic_e = function(stem)
local final = sub(stem, -1)
return not (
final == "e" or
final == "w" or
final == "r" and umatch(normalize(sub(stem, -2, -2)), "[" .. vowels .. "]")
)
end,
}
suffixes["r"] = {
final_y_is_i = true,
final_ey_is_i = true,
final_consonant_is_doubled = true,
epenthetic_e = default_epenthetic_e
}
suffixes["st.superlative"] = suffixes["r"]
-- Returns the stem used for suffixes that sometimes convert final "y"
-- into "i", such as "-es" ("-ies"), e.g. "penny" → "penni" ("pennies").
-- If `final_ey_is_i` is true, final "ey" may also be converted, e.g.
-- "plaguey" → "plagui"; this is needed for "-er" ("-ier") and "-est"
-- ("-iest").
local function convert_final_y_to_i(str, final_ey_is_i)
if sub(str, -1) ~= "y" then
return str
end
local final2 = sub(str, -2)
-- If `final_ey_is_i` is true, treat final "-ey" can also be reduced.
if final_ey_is_i and final2 == "ey" then
local final3 = sub(str, -3)
-- Special case: treat "eey" as "ee" + "y" (e.g. "treey" →
-- "treeiest"). This doesn't apply if `final_ey_is_i` is not true, as
-- `final_ey_is_i` assumes final "(e)y" is likely to be syllabic.
if final3 == "eey" then
return sub(str, 1, -2) .. "i"
end
local base_stem = sub(str, 1, -3)
-- Special case: allow final "-ey" ("potato-ey" → "potato-iest").
if final3 == "-ey" then
return base_stem .. "i"
end
-- Final "ey" becomes "i" iff the term is polysyllabic (e.g. not
-- "grey"). "ey" is common if the base stem ends in a vowel
-- ("echo" → "echoey"), so the presence of a vowel anywhere in the
-- base stem is sufficient to deem it polysyllabic. ("echoey" →
-- "echo" → "echoiest", "beigey" → "beig" → "beigiest", but
-- "grey" → "gr" → "greyest"). The first "y" in "-yey" can be
-- treated as a vowel as long as it's preceded by something
-- ("clayey" → "clay" → "clayiest", "cryey" → "cry" → "cryiest"),
-- so it needs to be treated as a special case.
local normalized = normalize(base_stem)
if sub(normalized, -1) == "y" then
if umatch(normalized, "[%w@][yY]$") then
return base_stem .. "i"
end
elseif umatch(normalized, "[" .. vowels .. "%d]%w*$") then
return base_stem .. "i"
end
-- Special cases: match final "-y" ("bro-y" → "bro-iest"), and "quy"
-- ("soliloquy" → "soliloquies").
elseif final2 == "-y" or sub(str, -3) == "quy" then
-- Otherwise, final "y" becomes "i" iff it's not preceded by a vowel
-- ("shy" → "shiest", "horsy" → "horsies", but "day" → "days", "coy" →
-- "coyest").
return sub(str, 1, -2) .. "i"
else
local base_stem = sub(str, 1, -2)
if umatch(normalize(base_stem), "[^%s%p" .. vowels .. "]$") then
return base_stem .. "i"
end
end
return str
end
local function double_final_consonant(str, final)
local initial = umatch(normalize(sub(str, 1, -2)), "^.*%f[^%z%s-–]([%l%p]*)[" .. vowels .. "]$")
if not initial then
return str
elseif initial == "" or initial == "y" then
return str .. final
elseif match(initial, "^.[\128-\191]*$") then
return umatch(initial, "[^" .. vowels .. "]") and (str .. final) or str
end
return umatch(initial, "^[^" .. vowels .. "]*%f[^%l]$") and (str .. final) or str
end
local function remove_silent_e(str)
local final2 = sub(str, -2)
if final2 == "ie" then
return sub(str, 1, -3) .. "y"
end
local base_stem = sub(str, 1, -2)
if final2 == "ue" then
return base_stem
end
return umatch(normalize(base_stem), "^.*[" .. vowels .. "][^" .. vowels .. "]+$") and base_stem or str
end
function export.add_suffix(term, suffix)
local data = suffixes[suffix]
suffix = match(suffix, "^([^.]*)")
local final, stem = sub(term, -1)
if data.final_y_is_i and final == "y" then
stem = convert_final_y_to_i(term, data.final_ey_is_i)
elseif data.final_consonant_is_doubled and match(final, "[bcdfgjklmnpqrstvz]") then
stem = double_final_consonant(term, final)
elseif data.remove_silent_e and final == "e" then
stem = remove_silent_e(term)
else
stem = term
end
local epenthetic_e = data.epenthetic_e
if epenthetic_e and epenthetic_e(stem, term) then
return stem .. "e" .. suffix
end
return stem .. suffix
end
add_suffix = export.add_suffix
--[==[
Pluralize a word in a smart fashion, according to normal English rules.
# If the word ends in a consonant or "qu" + "-y", replace "-y" with "-ies".
# If the word ends in "s", "x", "z", "ch", "sh" or "zh", add "-es".
# Otherwise, add "-s".
This handles links correctly:
# If a piped link, change the second part appropriately.
# If a non-piped link and rule #1 above applies, convert to a piped link with the second part containing the plural.
# If a non-piped link and rules #2 or #3 above apply, add the plural outside the link.
]==]
function export.pluralize(str)
-- Treat as a link if a "[[" is present and the string ends with "]]".
if not (find(str, "[[", 1, true) and sub(str, -2) == "]]") then
return add_suffix(str, "")
end
-- Find the last "[[" (in case there is more than one) by reversing
-- the string.
local str_rev = reverse(str)
local open = find(str_rev, "[[", 3, true)
-- If the last "[[" is followed by a "]]" which isn't at the end,
-- then the final "]]" is just plaintext (e.g. "[[foo]]bar]]").
local bad_close = find(str_rev, "]]", 3, true)
-- Note: the bad "]]" will have a lower index than the last "[[" in
-- the reversed string.
if bad_close and bad_close < open then
return add_suffix(str, "")
end
open = #str - open + 2
-- Get the target and display text by searching from just after "[[".
local target, display = match(str, "([^|]*)|?(.*)%]%]$", open)
display = add_suffix(display ~= "" and display or target, "")
-- If the link target is a substring of the display text, then
-- use a trail (e.g. "[[foo]]" → "[[foo]]s", since "foo" is a substring
-- of "foos").
local index, trail = find(display, target, 1, true)
if index == 1 then
return sub(str, 1, open - 1) .. target .. "]]" .. sub(display, trail + 1)
end
-- Otherwise, return a piped link.
return sub(str, 1, open - 1) .. target .. "|" .. display .. "]]"
end
return export