Module:kanjitab: difference between revisions

From Wiktionary, the free dictionary
Jump to navigation Jump to search
Content deleted Content added
Use kana range.
use CSS class floatright; remove unnecessary font-size:small
 
(29 intermediate revisions by 7 users not shown)
Line 1: Line 1:
local export = {}
local export = {}


local m_str_utils = require("Module:string utilities")
local m_utilities = require("Module:utilities")
local m_utilities = require("Module:utilities")
local m_ja = require("Module:ja")
local m_ja = require("Module:ja")
Line 8: Line 9:
]=]
]=]


local title = mw.title.getCurrentTitle()
local concat = table.concat
local convert_iteration_marks = require("Module:Hani").convert_iteration_marks
local find = string.find
local gsplit = m_str_utils.gsplit
local gsub = string.gsub
local kata_to_hira = m_ja.kata_to_hira
local insert = table.insert
local match = string.match
local remove = table.remove
local split = m_str_utils.split
local sub = string.sub
local ugsub = mw.ustring.gsub
local ulen = m_str_utils.len
local umatch = mw.ustring.match
local usub = m_str_utils.sub

local PAGENAME = mw.loadData("Module:headword/data").pagename
local PAGENAME = mw.loadData("Module:headword/data").pagename
local NAMESPACE = title.nsText
local NAMESPACE = mw.title.getCurrentTitle().nsText


local d_range = mw.loadData'Module:ja/data/range'
local d_range = mw.loadData("Module:ja/data/range")
local yomi_data = mw.loadData("Module:kanjitab/data")


local kanji_grade_links = {
local kanji_grade_links = {
Line 23: Line 40:
"[[Appendix:Japanese_glossary#jōyō_kanji|Grade: S]]", -- 7
"[[Appendix:Japanese_glossary#jōyō_kanji|Grade: S]]", -- 7
"[[Appendix:Japanese_glossary#jinmeiyō_kanji|Jinmeiyō]]", -- 8
"[[Appendix:Japanese_glossary#jinmeiyō_kanji|Jinmeiyō]]", -- 8
"[[Appendix:Japanese_glossary#hyōgaiji|Hyōgaiji]]" -- 9
"[[Appendix:Japanese_glossary#hyōgai_kanji|Hyōgai]]" -- 9
}
}

local function quote(text)
return "“" .. text .. "”"
end


-- this is the function that is called from templates
-- this is the function that is called from templates
Line 43: Line 56:
alt2 = {},
alt2 = {},
kyu = { list = true },
kyu = { list = true },
y = {alias_of = 'yomi'},
y = {alias_of = "yomi"},
clearright = {type = "boolean"},
clearright = {type = "boolean"},
pagename = {},
pagename = {},
Line 53: Line 66:


if args.pagename and NAMESPACE == "" then
if args.pagename and NAMESPACE == "" then
require'Module:debug'.track'kanjitab/pagename param in mainspace'
require("Module:debug/track")("kanjitab/pagename param in mainspace")
end
end
local pagename = args.pagename or PAGENAME
local pagename = args.pagename or PAGENAME
Line 63: Line 76:
local kanji = {}
local kanji = {}
local non_kanji = {}
local non_kanji = {}

-- 々 and 〻
pagename = convert_iteration_marks(pagename)
local kanji_border = 1
local kanji_border = 1
mw.ustring.gsub(pagename, '()([' .. d_range.kanji .. '々])()', function(p1, w1, p2)
ugsub(pagename, "()([" .. d_range.kanji .. "])()", function(p1, w1, p2)
table.insert(non_kanji, mw.ustring.sub(pagename, kanji_border, p1 - 1))
insert(non_kanji, usub(pagename, kanji_border, p1 - 1))
kanji_border = p2
kanji_border = p2
table.insert(kanji, w1)
insert(kanji, w1)
end)
end)
table.insert(non_kanji, mw.ustring.sub(pagename, kanji_border))
insert(non_kanji, usub(pagename, kanji_border))


-- kyujitai
-- kyujitai
local kyu = args.kyu
local kyu = args.kyu
if kyu[1] == '-' then
if kyu[1] == "-" then
kyu = {}
kyu = {}
elseif kyu[1] == nil then
elseif kyu[1] == nil then
local form_kyu = {non_kanji[1]}
local form_kyu = {non_kanji[1]}
local kyu_data = mw.loadData('Module:ja/data/kyu')
local kyu_data = mw.loadData("Module:ja/data/kyu")
local has_kyu, has_kyu_nonsupple, has_shin = false, false, false
local has_kyu, has_kyu_nonsupple, has_shin = false, false, false
for i, v in ipairs(kanji) do
for i, v in ipairs(kanji) do
local v_kyu = kyu_data[1]:match(v .. '(%S*)%s')
local v_kyu = match(kyu_data[1], v .. "(%S*)%s")
if v_kyu == nil then
if v_kyu == nil then
table.insert(form_kyu, v)
insert(form_kyu, v)
elseif v_kyu == '' then
elseif v_kyu == "" then
has_shin = true
has_shin = true
break
break
elseif v_kyu:sub(1, 1) == '&' then
elseif v_kyu:sub(1, 1) == "&" then
has_kyu = true
has_kyu = true
table.insert(form_kyu, v_kyu)
insert(form_kyu, v_kyu)
else
else
has_kyu, has_kyu_nonsupple = true, true
has_kyu, has_kyu_nonsupple = true, true
table.insert(form_kyu, v_kyu)
insert(form_kyu, v_kyu)
end
end
table.insert(form_kyu, non_kanji[i + 1])
insert(form_kyu, non_kanji[i + 1])
end
end


if not has_shin and has_kyu then
if not has_shin and has_kyu then
kyu[1] = (has_kyu_nonsupple and '' or pagename .. '|') .. table.concat(form_kyu)
kyu[1] = (has_kyu_nonsupple and "" or pagename .. "|") .. concat(form_kyu)
end
end


if pagename:match'' then
if find(pagename, "") then
require'Module:debug'.track'kanjitab/ambiguous kyujitai for 弁'
require("Module:debug/track")("kanjitab/ambiguous kyujitai for 弁")
kyu[1] = 'which 弁?'
kyu[1] = "which 弁?"
end
end
end
end

local all_yomi, missing_yomi
-- 々
if args.yomi then
for i, v in ipairs(kanji) do
all_yomi = {}
if v == '々' then kanji[i] = kanji[i - 1] end
local keys = split(args.yomi, ",")
for i, yomi, len in ipairs(keys) do
yomi, len = match(yomi, "^(%l*)(%d*)$")
yomi = yomi_data[yomi] or error("The yomi type \"" .. yomi .. "\" in the input \"" .. args.yomi .. "\" is not recognized.")
if len ~= "" then
-- Disallow length 0 or leading zeroes, as a sanity check.
len = match(len, "^[1-9]%d*$") and tonumber(len) or error("Cannot specify a length of " .. len .. " kanji.")
-- Only one yomi with no length given: apply to all kanji.
elseif i == 1 and #keys == 1 then
len = #kanji
else
len = 1
end
local yomi_type = yomi.type
-- If the on'yomi is not specified as goon/kanon/toon/soon, only "on".
if yomi_type == "on'yomi" then
require("Module:debug/track")("kanjitab/unspecified on")
elseif yomi_type == "jūbakoyomi" then
require("Module:debug/track")("kanjitab/jubakoyomi")
elseif yomi_type == "yutōyomi" then
require("Module:debug/track")("kanjitab/yutoyomi")
end
-- If the yomi requires a specific number of kanji (e.g. jūbakoyomi, yutōyomi).
local req_kanji = yomi.required_kanji
if req_kanji and #kanji ~= req_kanji then
error("The yomi type \"" .. yomi.type .. "\" is only applicable to terms with " .. req_kanji .. " kanji.")
elseif yomi.type == "none" then
missing_yomi = true
end
-- Insert yomi data for each applicable kanji. Wrap in a table first, as the range for this input yomi is determined by its identity, so that (e.g.) "kun,kun" is still treated as two separate inputs.
yomi = {data = yomi}
for _ = 1, len do
insert(all_yomi, yomi)
end
end
-- If there are any yomi slots left, handle them as empty.
if #all_yomi < #kanji then
missing_yomi = true
for _ = #all_yomi + 1, #kanji do
insert(all_yomi, {data = yomi_data.none})
end
end
elseif #kanji > 0 then
missing_yomi = true
end
end

if missing_yomi then
insert(categories, lang_name .. " terms with missing yomi")
end
-- process readings
-- process readings
local readings = {}
local readings = {}
Line 117: Line 185:
local reading_length_total = 0
local reading_length_total = 0
for i = 1, args[1].maxindex do
for i = 1, args[1].maxindex do
local reading_kana, reading_length
local reading_kana, reading_length = match(args[1][i] or "", "^(%D*)(%d*)$")
_, _, reading_kana, reading_length = mw.ustring.find(args[1][i] or '', '^([^0-9]*)([0-9]*)$')
reading_kana = reading_kana ~= "" and reading_kana or nil
reading_kana = reading_kana ~= "" and reading_kana or nil
reading_length = reading_kana and tonumber(reading_length) or 1
reading_length = reading_kana and tonumber(reading_length) or 1


table.insert(readings, {reading_kana, reading_length})
insert(readings, {reading_kana, reading_length})
reading_length_total = reading_length_total + reading_length
reading_length_total = reading_length_total + reading_length
end
end
if reading_length_total > #kanji then
if reading_length_total > #kanji then
error('Readings for ' .. reading_length_total .. ' kanji are given, but this word has only ' .. #kanji .. ' kanji.')
error("Readings for " .. reading_length_total .. " kanji are given, but this word has only " .. #kanji .. " kanji.")
else
else
for i = reading_length_total + 1, #kanji do table.insert(readings, {nil, 1}) end
for _ = reading_length_total + 1, #kanji do
insert(readings, {nil, 1})
end
end
end


local table_head = [=[
local table_head = [=[
{| class="wikitable kanji-table" style="text-align: center; font-size: small; float: right;]=] .. (args.clearright and ' clear:right;' or '') .. [=["
{| class="wikitable kanji-table floatright" style="text-align: center; ]=] .. (args.clearright and " clear:right;" or "") .. [=["
! ]=] .. (#kanji > 1 and 'colspan="' .. #kanji .. '" ' or '') .. [=[style="font-weight: normal;" | [[Appendix:Japanese_glossary#kanji|Kanji]] in this term
! ]=] .. (#kanji > 1 and "colspan=\"" .. #kanji .. "\" " or "") .. [=[style="font-weight: normal;" | [[Appendix:Japanese_glossary#kanji|Kanji]] in this term
|- lang="]=] .. lang_code .. [=[" class="Jpan" style="font-size: 2em; background: white; line-height: 1em;"
|- lang="]=] .. lang_code .. [=[" class="Jpan" style="font-size: 2em; background: white; line-height: 1em;"


]=]
]=]

local yomi

-- on/kun is jūbakoyomi; NOTE: these are only applicable for two-kanji compounds
-- kun/on is yutōyomi; NOTE: these are only applicable for two-kanji compounds
if args.yomi then
yomi = {}
local extended_yomi_code = {
o = 'on', on = 'on',
kanon = 'kanon', -- kan is kan'yoon, and ko is kun+on for backward compatibility
goon = 'goon',
soon = 'soon',
toon = 'toon',
kan = 'kanyoon', kanyo = 'kanyoon', kanyoon = 'kanyoon',
k = 'kun', kun = 'kun',
juku = 'jukujikun', jukuji = 'jukujikun', jukujikun = 'jukujikun',
ok = "jūbakoyomi", j = "jūbakoyomi",
ko = "yutōyomi", y = "yutōyomi", yu = "yutōyomi",
i = 'irregular', irr = 'irregular', irreg = 'irregular', irregular = 'irregular',
n = 'nanori', nanori = 'nanori',
[''] = '', none = '',
}
for i in mw.text.gsplit(args.yomi, ',') do
local _, _, a, b = mw.ustring.find(i, '^([a-z]*)([0-9]*)$')
a = extended_yomi_code[a] or error("The yomi type “" .. args.yomi .. "” is not recognized.")
b = tonumber(b) or 1
table.insert(yomi, { a, b })

if (a == "jūbakoyomi" or a == "yutōyomi") and #kanji ~= 2 then
error'yutou or juubako is only applicable to 二字熟語'
end
end
if #yomi == 1 and #kanji > 1 then
yomi[1][2] = #kanji
end
elseif #kanji > 0 then
require'Module:debug'.track'kanjitab/no yomi'
end

if args.k.maxindex and args.k.maxindex > args[1].maxindex then
if args.k.maxindex and args.k.maxindex > args[1].maxindex then
error'kanjitab/too many k'
error("kanjitab/too many k")
end
end

if args.o.maxindex and args.o.maxindex > args[1].maxindex then
if args.o.maxindex and args.o.maxindex > args[1].maxindex then
error'kanjitab/too many o'
error("kanjitab/too many o")
end
end

local yomi_type_by_kanji = {}
if yomi then
for i = 1, #yomi do
for j = 1, yomi[i][2] do
table.insert(yomi_type_by_kanji, yomi[i][1])
end
end
else
for i = 1, #kanji do
table.insert(yomi_type_by_kanji, '')
end
end

local is_ateji = {}
local is_ateji = {}
if args.ateji then
if args.ateji then
local ateji = args.ateji
local ateji = args.ateji
local cat_ateji = false
local cat_ateji = false
if ateji == 'y' then
if ateji == "y" then
for i = 1, #kanji do
for i = 1, #kanji do
is_ateji[i] = true
is_ateji[i] = true
Line 207: Line 225:
cat_ateji = true
cat_ateji = true
else
else
for i in mw.text.gsplit(ateji, ';') do
for i in gsplit(ateji, ";") do
string.gsub(i, '^([0-9]+)$', function(a)
gsub(i, "^(%d+)$", function(a)
is_ateji[tonumber(a)] = true
is_ateji[tonumber(a)] = true
cat_ateji = true
cat_ateji = true
end)
end)
string.gsub(i, '^([0-9]+),([0-9]+)$', function (a, b)
gsub(i, "^(%d+),(%d+)$", function (a, b)
for j = tonumber(a), tonumber(b) do
for j = tonumber(a), tonumber(b) do
is_ateji[j] = true
is_ateji[j] = true
Line 220: Line 238:
end
end
end
end
if cat_ateji then table.insert(categories, lang_name .. " terms spelled with ateji") end
if cat_ateji then insert(categories, lang_name .. " terms spelled with ateji") end
end
end


Line 234: Line 252:


if reading_length <= 1 then
if reading_length <= 1 then
table.insert(cell, '| rowspan="2" | ')
insert(cell, "| rowspan=\"2\" | ")
else
else
table.insert(cell, '| colspan ="' .. reading_length .. '" | ')
insert(cell, "| colspan =\"" .. reading_length .. "\" | ")
end
end


-- display reading, actual reading and okurigana
-- display reading, actual reading and okurigana
if reading_kana then
if reading_kana then
if reading_kana ~= '' and mw.ustring.match(reading_kana, '[^' .. d_range.kana .. ']') then
if reading_kana ~= "" and reading_kana ~= "-" and umatch(reading_kana, "[^" .. d_range.kana .. "]") then
error("Please remove any non-kana characters from the reading input " .. reading_kana .. ".")
require'Module:debug'.track'kanjitab/not all kana'
end
end


Line 252: Line 270:
local text = reading_kana .. okurigana_text .. actual_reading_text
local text = reading_kana .. okurigana_text .. actual_reading_text


readings_actual[i] = {(actual_reading or reading_kana) .. (okurigana or ''), reading_length}
readings_actual[i] = {(actual_reading or reading_kana) .. (okurigana or ""), reading_length}


table.insert(cell, '<span class="Jpan" lang="' .. lang_code .. '">' .. text .. '</span>')
insert(cell, "<span class=\"Jpan\" lang=\"" .. lang_code .. "\">" .. text .. "</span>")
if reading_length <= 1 then table.insert(cell, '<br/>') end
if reading_length <= 1 then insert(cell, "<br/>") end
else
else
readings_actual[i] = {nil, 1}
readings_actual[i] = {nil, 1}
Line 264: Line 282:
local single_kanji = kanji[j]
local single_kanji = kanji[j]
local kanji_grade = m_ja.kanji_grade(single_kanji)
local kanji_grade = m_ja.kanji_grade(single_kanji)
local ateji_text = is_ateji[j] and '<br/><small>([[Appendix:Japanese glossary#ateji|ateji]])</small>' or ''
local ateji_text = is_ateji[j] and "<br/><small>([[Appendix:Japanese glossary#ateji|ateji]])</small>" or ""

local type, compound
if reading_kana then
if all_yomi then
-- subcategorize by reading if this is joyo kanji, not doing that for less common kanji, with exceptions
local yomi = all_yomi[j].data
if (kanji_grade < 8 or (
type, compound = yomi.type, yomi.compound_reading
'厭昌之芽昌浩智晃淳敦聡晃旭亮糊桂隘阿唖撫鼠阿耘迂寅已伊餡姦闊礙碍凱亥价謳嘔齧日臣桶抉兎鵜卯綾飴焙肋鮫頚糞軋烏痒捷辰叩橙揃嶋澤菱彦囃覗呑之乃鼠做寅樋堤槌机杖頼辿哉叢狢峯巳卍鱒仄他惚弘宏燕倦經痙圭禽僑鋸醵墟屹綺几翫癌劫膠昂鹸牽喧餐鑽瑣些渾梱坤國壕誦哨蒐杓爾梓荼楕躁綜楚闡閃撰專泄藉棲錘錐祷盪淘點顛填擲擢闖厨蛋潭腿冪碧劈焚祓弗憑誹砒婢挽拔撥剥胚播乃狼牢蓮礫醂龍榴蕾酉祐佑耶也蔓曼沫邁呆硼牡甫步矮狸苔'
end
):find(single_kanji)) and yomi_type_by_kanji[j] ~= 'irregular' and yomi_type_by_kanji[j] ~= 'jukujikun' and reading_length == 1 then
table.insert(categories, lang_name .. " terms spelled with " .. single_kanji .. " read as " .. reading_kana)
if not reading_kana then
else
if type ~= "irregular" then
table.insert(categories, lang_name .. " terms spelled with " .. single_kanji)
require("Module:debug/track")("kanjitab/no reading")
end
else
if yomi_type_by_kanji[j] ~= 'irregular' and yomi_type_by_kanji[j] ~= 'jukujikun' then
require'Module:debug'.track'kanjitab/no reading'
end
end
table.insert(categories, lang_name .. " terms spelled with " .. single_kanji)
insert(categories, lang_name .. " terms spelled with " .. single_kanji)
elseif reading_length ~= 1 or type == "irregular" then
insert(categories, lang_name .. " terms spelled with " .. single_kanji)
elseif compound then
-- Re-enable once all bad jukujikun calls are fixed.
-- error("The yomi type \"" .. type .. "\" is only applicable to compound character readings, so cannot apply to " .. single_kanji .. " read as " .. reading_kana .. ". If this is intended as part of a " .. type .. " reading, please enter the whole reading as one, followed by the number of kanji it applies to.")
require("Module:debug/track")("kanjitab/single kanji with jukujikun")
else -- Subcategorize by reading.
insert(categories, lang_name .. " terms spelled with " .. single_kanji .. " read as " .. kata_to_hira(reading_kana))
end
end


if reading_length <= 1 then
if reading_length <= 1 then
table.insert(cell, "<small>" .. kanji_grade_links[kanji_grade] .. "</small>" .. ateji_text)
insert(cell, "<small>" .. kanji_grade_links[kanji_grade] .. "</small>" .. ateji_text)
else
else
table.insert(cells_below, "| <small>" .. kanji_grade_links[kanji_grade] .. "</small>" .. ateji_text)
insert(cells_below, "| <small>" .. kanji_grade_links[kanji_grade] .. "</small>" .. ateji_text)
end
end
end
end
table.insert(cells_above, table.concat(cell))
insert(cells_above, concat(cell))
kanji_pos = kanji_pos + reading_length
kanji_pos = kanji_pos + reading_length
end
end
table.insert(cells, '|- style="background: white;"')
insert(cells, "|- style=\"background: white;\"")
if #cells_below > 0 then
if #cells_below > 0 then
table.insert(cells, table.concat(cells_above, '\n'))
insert(cells, concat(cells_above, "\n"))
table.insert(cells, '|- style="background: white;"')
insert(cells, "|- style=\"background: white;\"")
table.insert(cells, table.concat(cells_below, '\n'))
insert(cells, concat(cells_below, "\n"))
else
else
for i, v in ipairs(cells_above) do
for i, v in ipairs(cells_above) do
cells_above[i] = v:gsub('| rowspan="2" | ', '| ')
cells_above[i] = gsub(v, "| rowspan=\"2\" | ", "| ")
end
end
table.insert(cells, table.concat(cells_above, '\n'))
insert(cells, concat(cells_above, "\n"))
end
end

local yomi_info = {
["on"] = {
text = "on’yomi",
entry = "Appendix:Japanese_glossary#on'yomi",
category = lang_name .. " terms read with on'yomi",
},
["kanon"] = {
text = "kan’on",
entry = "Appendix:Japanese_glossary#kan'on",
category = lang_name .. " terms read with on'yomi",
},
["goon"] = {
text = "goon",
entry = "Appendix:Japanese_glossary#goon",
category = lang_name .. " terms read with on'yomi",
},
["soon"] = {
text = "sōon",
entry = "Appendix:Japanese_glossary#tōon",
category = lang_name .. " terms read with on'yomi",
},
["toon"] = {
text = "tōon",
entry = "Appendix:Japanese_glossary#tōon",
category = lang_name .. " terms read with on'yomi",
},
["kun"] = {
text = "kun’yomi",
entry = "Appendix:Japanese_glossary#kun'yomi",
category = lang_name .. " terms read with kun'yomi",
},
["nanori"] = {
text = "nanori",
entry = "Appendix:Japanese_glossary#nanori",
category = lang_name .. " terms read with nanori",
},
["yutōyomi"] = {
text = "yutōyomi",
entry = "Appendix:Japanese_glossary#yutōyomi",
category = lang_name .. " terms read with yutōyomi",
},
["jūbakoyomi"] = {
text = "jūbakoyomi",
entry = "Appendix:Japanese_glossary#jūbakoyomi",
category = lang_name .. " terms read with jūbakoyomi",
},
["jukujikun"] = {
text = "jukujikun",
entry = "Appendix:Japanese_glossary#jukujikun",
category = lang_name .. " terms read with jukujikun",
},
["irregular"] = {
text = "''irregular''",
category = lang_name .. " terms with irregular kanji readings",
},
["kanyoon"] = {
text = "kan’yōon",
entry = "Appendix:Japanese_glossary#kan'yoon",
category = lang_name .. " terms read with kan'yōon",
},
}

local rendaku = args.r
local rendaku = args.r
if rendaku then
if rendaku then
table.insert(categories, lang_name .. " terms with rendaku")
insert(categories, lang_name .. " terms with rendaku")
end
end

if yomi then
if all_yomi then
table.insert(cells, "|-")
insert(cells, "|-")
local len, all_on, yomi_cat = 1, true
for _, i in ipairs(yomi) do
for i, yomi in ipairs(all_yomi) do
local yomi_info = yomi_info[i[1]] or { text = i[1] }
-- If the next kanji has the same yomi table, it's part of the same range.
local text
if yomi_info.entry then
if yomi == all_yomi[i + 1] then
len = len + 1
text = "[[" .. yomi_info.entry .. "|" .. yomi_info.text .. "]]"
else
else
text = yomi_info.text
yomi = yomi.data
local yomi_type = yomi.type
local display = yomi.display or yomi_type
local appendix = yomi.appendix
insert(cells, "| colspan=\"" .. len .. "\" |" .. (
appendix == false and display or
"[[Appendix:Japanese_glossary#" .. (appendix or yomi_type) .. "|" .. display .. "]]"
))
-- Categorise as irregular if any irregular yomi are found; otherwise, categorise if all yomi are of the same type. If yomi are of different types but are all on, on'yomi is used as a fallback.
if yomi_cat ~= "irregular" then
local cat_type = yomi_type
if cat_type == "irregular" or yomi_cat == nil then
yomi_cat = cat_type
elseif yomi_cat ~= cat_type then
yomi_cat = false
end
if not yomi.onyomi then
all_on = false
end
end
len = 1
end
end
table.insert(cells, '| colspan="' .. i[2] .. '" |' .. text)
end
end
if yomi_cat then
local is_onyomi = { on = true, kanon = true, goon = true, soon = true, toon = true, kanyoon = true }
-- Check yomi_data first, in case cat_type is "irregular"; if no match, must be some other type, so get it from the first yomi in all_yomi, since not all yomi types are yomi_data keys.
-- categories
yomi_cat = yomi_data[yomi_cat] or all_yomi[1].data
local all_onyomi = true
elseif all_on then
for i = 1, #yomi do
yomi_cat = yomi_data.on
if not is_onyomi[yomi[i][1]] then all_onyomi = false; break end
elseif #all_yomi == 2 then
end
local y1, y2 = all_yomi[1].data, all_yomi[2].data
if all_onyomi then
if ulen(pagename) == 2 then
table.insert(categories, yomi_info.on.category)
if y1.onyomi and y2.type == "kun'yomi" then
elseif yomi[1][1] == 'jūbakoyomi' or yomi[1][1] == 'yutōyomi' then
yomi_cat = yomi_data.j -- jūbakoyomi
table.insert(categories, yomi_info[yomi[1][1]].category)
elseif y1.type == "kun'yomi" and y2.onyomi then
else
yomi_cat = yomi_data.y -- yutōyomi
local all_yomi_of_same_type = true
for i = 2, #yomi do
if yomi[i][1] ~= yomi[1][1] then all_yomi_of_same_type = false; break end
end
if all_yomi_of_same_type then
table.insert(categories, yomi_info[yomi[1][1]].category)
elseif #yomi == 2 and yomi[1][2] == 1 and yomi[2][2] == 1 and mw.ustring.len(pagename) == 2 then
if is_onyomi[yomi[1][1]] and yomi[2][1] == 'kun' then
table.insert(categories, yomi_info["jūbakoyomi"].category)
elseif yomi[1][1] == 'kun' and is_onyomi[yomi[2][1]] then
table.insert(categories, yomi_info["yutōyomi"].category)
end
end
end
end
if yomi_cat then
local category = yomi_cat.reading_category
if category ~= false then
insert(categories, lang_name .. " " .. "terms read with " .. (category or yomi_cat.type))
end
end
end
end
Line 413: Line 389:
kanji_table = table_head
kanji_table = table_head
for _, v in ipairs(kanji) do
for _, v in ipairs(kanji) do
kanji_table = kanji_table .. '| style="padding: 0.5em;" | [[' .. v .. '#' .. lang_name .. '|' .. v .. ']]\n'
kanji_table = kanji_table .. "| style=\"padding: 0.5em;\" | [[" .. v .. "#" .. lang_name .. "|" .. v .. "]]\n"
end
end
kanji_table = kanji_table .. table.concat(cells, '\n') .. '\n|}'
kanji_table = kanji_table .. concat(cells, "\n") .. "\n|}"
else
else
kanji_table = ''
kanji_table = ""
end
end


local forms_table = ""
local forms_table = ""
if args.alt == '' or args.alt == '-' then args.alt = nil end
if args.alt == "" or args.alt == "-" then args.alt = nil end
if kyu[1] or args.alt then
if kyu[1] or args.alt then
local forms = {}
local forms = {}


-- |kyu=
-- |kyu=
if kyu[1] == 'which 弁?' then
if kyu[1] == "which 弁?" then
table.insert(forms, '<strong class="error" style="font-size:75%;">Please specify the correct kyujitai for 弁 with the parameter "kyu".</strong>[[Category:Requests for cleanup in ' .. lang_name .. ' entries]]')
insert(forms, "<strong class=\"error\" style=\"font-size:75%;\">Please specify the correct kyujitai for 弁 with the parameter \"kyu\".</strong>[[Category:Requests for cleanup in " .. lang_name .. " entries]]")
table.remove(kyu, 1)
remove(kyu, 1)
end
end


for _, form in ipairs(kyu) do
for _, form in ipairs(kyu) do
local form_linkto, form_display = form:match'^(.+)|(.+)$'
local form_linkto, form_display = match(form, "^(.+)|(.+)$")
if not form_linkto then form_linkto, form_display = form, form end
if not form_linkto then form_linkto, form_display = form, form end
table.insert(forms, table.concat{
insert(forms, concat{
'<span class="Jpan" lang="' .. lang_code .. '" style="font-family:游ゴシック, HanaMinA, sans-serif; font-size:140%;">[[',
"<span class=\"Jpan\" lang=\"" .. lang_code .. "\" style=\"font-family:游ゴシック, HanaMinA, sans-serif; font-size:140%;\">[[",
form_linkto,
form_linkto,
form_linkto == pagename and '|' or '#' .. lang_name .. '|',
form_linkto == pagename and "|" or "#" .. lang_name .. "|",
form_display,
form_display,
']]</span> <small>',
"]]</span> <small>",
show_labels {labels = {'kyūjitai'}, lang = lang, nocat = true },
show_labels {labels = {"kyūjitai"}, lang = lang, nocat = true },
'</small>',
"</small>",
})
})
end
end
Line 447: Line 423:
-- |alt=
-- |alt=
if args.alt then
if args.alt then
for form in mw.text.gsplit(args.alt, ',') do
for form in gsplit(args.alt, ",") do
local i_semicolon = string.find(form, ':')
local i_semicolon = find(form, ":")
if i_semicolon then
if i_semicolon then
local altform = string.sub(form, 1, i_semicolon - 1)
local altform = sub(form, 1, i_semicolon - 1)
local altlabels = mw.text.split(string.sub(form, i_semicolon + 1), ' ')
local altlabels = split(sub(form, i_semicolon + 1), " ")
table.insert(forms, table.concat{
insert(forms, concat{
'<span class="Jpan" lang="' .. lang_code .. '" style="font-size:140%">[[',
"<span class=\"Jpan\" lang=\"" .. lang_code .. "\" style=\"font-size:140%\">[[",
altform,
altform,
'#' .. lang_name .. '|',
"#" .. lang_name .. "|",
altform,
altform,
']]</span> <small>',
"]]</span> <small>",
show_labels { labels = altlabels, lang = lang, nocat = true },
show_labels { labels = altlabels, lang = lang, nocat = true },
'</small>',
"</small>",
})
})
else
else
table.insert(forms, table.concat{
insert(forms, concat{
'<span class="Jpan" lang="' .. lang_code .. '" style="font-size:140%">[[',
"<span class=\"Jpan\" lang=\"" .. lang_code .. "\" style=\"font-size:140%\">[[",
form,
form,
'#' .. lang_name .. '|',
"#" .. lang_name .. "|",
form,
form,
']]</span>'
"]]</span>"
})
})
end
end
Line 473: Line 449:
end
end


forms_table = '\n' .. [[{| class="wikitable floatright"
forms_table = "\n" .. [[{| class="wikitable floatright"
! style="font-weight:normal" | Alternative spelling]] .. (#forms == 1 and "" or "s") .. [[
! style="font-weight:normal" | Alternative spelling]] .. (#forms == 1 and "" or "s") .. [[


|-
|-
| style="text-align:center;font-size:108%" | ]] .. table.concat(forms, '<br>') .. '\n|}'
| style="text-align:center;font-size:108%" | ]] .. concat(forms, "<br>") .. "\n|}"
end
end
Line 483: Line 459:
if args.alt2 and args.alt2 ~= "" and args.alt2 ~= "-" then
if args.alt2 and args.alt2 ~= "" and args.alt2 ~= "-" then
local forms2 = {}
local forms2 = {}
for form in mw.text.gsplit(args.alt2, ',') do
for form in gsplit(args.alt2, ",") do
table.insert(forms2, '<span class="Jpan" lang="' .. lang_code .. '">[[' .. form .. '#' .. lang_name .. '|' .. form .. ']]</span>')
insert(forms2, "<span class=\"Jpan\" lang=\"" .. lang_code .. "\">[[" .. form .. "#" .. lang_name .. "|" .. form .. "]]</span>")
end
end
forms_table2 = '\n' .. [[{| class="wikitable floatright"
forms_table2 = "\n" .. [[{| class="wikitable floatright"
! style="font-weight:normal" | Variant form]] .. (#forms2 == 1 and "" or "s") .. '\n' .. [[
! style="font-weight:normal" | Variant form]] .. (#forms2 == 1 and "" or "s") .. "\n" .. [[
| style="text-align:center;font-size:140%" | ]] .. table.concat(forms2, '<br>') .. '\n|}'
| style="text-align:center;font-size:140%" | ]] .. concat(forms2, "<br>") .. "\n|}"
end
end


Line 503: Line 479:
for _, v in ipairs(readings_actual) do
for _, v in ipairs(readings_actual) do
id = id + v[2]
id = id + v[2]
table.insert(sortkey, (v[1] or '') .. (non_kanji[id] or ''))
insert(sortkey, (v[1] or "") .. (non_kanji[id] or ""))
end
end
sortkey = table.concat(sortkey)
sortkey = concat(sortkey)
end
end
if sortkey == "" then
if sortkey == "" then

Latest revision as of 19:14, 17 July 2024


This module generates the content of {{ja-kanjitab}}, {{ryu-kanjitab}} etc.

Testcases

Kanji in this term
なつ
Grade: S
kun'yomi
Alternative spelling
懷かしい (kyūjitai)
Kanji in this term
と(り)
Grade: 3
し(まり)
Grade: S
kun'yomi
Kanji in this term

Grade: 1
つ(け) > づ(け)
Grade: 4
kun'yomi


Kanji in this term
かん
Grade: 4
けい
Grade: 3
kan'on
Alternative spelling
關係 (kyūjitai)
Kanji in this term
とう
Grade: 2
きょう
Grade: 2
kan'on goon
Kanji in this term
やまと こと
Grade: 2
は > ば
Grade: 3
Grade: 1 Grade: 3
jukujikun kun'yomi


Kanji in this term

Grade: S
(ateji)
ちゃ
Grade: 2
(ateji)

Grade: 3
(ateji)
ちゃ
Grade: 2
(ateji)
irregular kan'yōon goon kan'yōon
Kanji in this term

Jinmeiyō
(ateji)
ふつ > ふ
Hyōgai
(ateji)

Grade: 4
(ateji)

Grade: 4
(ateji)
けい
Grade: 6

Grade: S
(ateji)

Grade: 2
(ateji)

Grade: 4
(ateji)

Grade: 4
(ateji)
じん
Grade: 1
on'yomi irregular on'yomi kan'on kan'on kan'on irregular on'yomi kan'on kan'on
Alternative spelling
阿弗利加系亞米利加人 (kyūjitai)


{{ja-kanjitab|pagename=大和言葉|||こと|は|k4=ば|yomi=juku2,k2}}
{{ja-kanjitab|pagename=大和言葉|やまと2|こと|は|k3=ば|yomi=juku2,k2}} // "3" is # of argument, not # of kanji

local export = {}

local m_str_utils = require("Module:string utilities")
local m_utilities = require("Module:utilities")
local m_ja = require("Module:ja")
local show_labels = require("Module:labels").show_labels
--[=[
	Other modules used: [[Module:parameters]]
]=]

local concat = table.concat
local convert_iteration_marks = require("Module:Hani").convert_iteration_marks
local find = string.find
local gsplit = m_str_utils.gsplit
local gsub = string.gsub
local kata_to_hira = m_ja.kata_to_hira
local insert = table.insert
local match = string.match
local remove = table.remove
local split = m_str_utils.split
local sub = string.sub
local ugsub = mw.ustring.gsub
local ulen = m_str_utils.len
local umatch = mw.ustring.match
local usub = m_str_utils.sub

local PAGENAME = mw.loadData("Module:headword/data").pagename
local NAMESPACE = mw.title.getCurrentTitle().nsText

local d_range = mw.loadData("Module:ja/data/range")
local yomi_data = mw.loadData("Module:kanjitab/data")

local kanji_grade_links = {
	"[[Appendix:Japanese_glossary#kyōiku_kanji|Grade: 1]]",
	"[[Appendix:Japanese_glossary#kyōiku_kanji|Grade: 2]]",
	"[[Appendix:Japanese_glossary#kyōiku_kanji|Grade: 3]]",
	"[[Appendix:Japanese_glossary#kyōiku_kanji|Grade: 4]]",
	"[[Appendix:Japanese_glossary#kyōiku_kanji|Grade: 5]]",
	"[[Appendix:Japanese_glossary#kyōiku_kanji|Grade: 6]]",
	"[[Appendix:Japanese_glossary#jōyō_kanji|Grade: S]]",		-- 7
	"[[Appendix:Japanese_glossary#jinmeiyō_kanji|Jinmeiyō]]",	-- 8
	"[[Appendix:Japanese_glossary#hyōgai_kanji|Hyōgai]]"		-- 9
}

-- this is the function that is called from templates
function export.show(frame)
	local args = require("Module:parameters").process(frame:getParent().args, {
		[1] = { list = true, allow_holes = true },
		k = { list = true, allow_holes = true },
		o = { list = true, allow_holes = true },
		r = {},
		sort = {},
		yomi = {},
		ateji = {},
		alt = {},
		alt2 = {},
		kyu = { list = true },
		y = {alias_of = "yomi"},
		clearright = {type = "boolean"},
		pagename = {},
	})
	
	local lang_code = frame.args[1]
	local lang = require("Module:languages").getByCode(lang_code)
	local lang_name = lang:getCanonicalName()

	if args.pagename and NAMESPACE == "" then
		require("Module:debug/track")("kanjitab/pagename param in mainspace")
	end
	local pagename = args.pagename or PAGENAME

	local categories = {}
	local cells = {}

	-- extract kanji and non-kanji
	local kanji = {}
	local non_kanji = {}
	
	-- 々 and 〻
	pagename = convert_iteration_marks(pagename)
	
	local kanji_border = 1
	ugsub(pagename, "()([" .. d_range.kanji .. "々〻])()", function(p1, w1, p2)
		insert(non_kanji, usub(pagename, kanji_border, p1 - 1))
		kanji_border = p2
		insert(kanji, w1)
	end)
	insert(non_kanji, usub(pagename, kanji_border))

	-- kyujitai
	local kyu = args.kyu
	if kyu[1] == "-" then
		kyu = {}
	elseif kyu[1] == nil then
		local form_kyu = {non_kanji[1]}
		local kyu_data = mw.loadData("Module:ja/data/kyu")
		local has_kyu, has_kyu_nonsupple, has_shin = false, false, false
		for i, v in ipairs(kanji) do
			local v_kyu = match(kyu_data[1], v .. "(%S*)%s")
			if v_kyu == nil then
				insert(form_kyu, v)
			elseif v_kyu == "" then
				has_shin = true
				break
			elseif v_kyu:sub(1, 1) == "&" then
				has_kyu = true
				insert(form_kyu, v_kyu)
			else
				has_kyu, has_kyu_nonsupple = true, true
				insert(form_kyu, v_kyu)
			end
			insert(form_kyu, non_kanji[i + 1])
		end

		if not has_shin and has_kyu then
			kyu[1] = (has_kyu_nonsupple and "" or pagename .. "|") .. concat(form_kyu)
		end

		if find(pagename, "弁") then
			require("Module:debug/track")("kanjitab/ambiguous kyujitai for 弁")
			kyu[1] = "which 弁?"
		end
	end
	
	local all_yomi, missing_yomi
	if args.yomi then
		all_yomi = {}
		
		local keys = split(args.yomi, ",")
		for i, yomi, len in ipairs(keys) do
			yomi, len = match(yomi, "^(%l*)(%d*)$")
			yomi = yomi_data[yomi] or error("The yomi type \"" .. yomi .. "\" in the input \"" .. args.yomi .. "\" is not recognized.")
			if len ~= "" then
				-- Disallow length 0 or leading zeroes, as a sanity check.
				len = match(len, "^[1-9]%d*$") and tonumber(len) or error("Cannot specify a length of " .. len .. " kanji.")
			-- Only one yomi with no length given: apply to all kanji.
			elseif i == 1 and #keys == 1 then
				len = #kanji
			else
				len = 1
			end
			
			local yomi_type = yomi.type
			-- If the on'yomi is not specified as goon/kanon/toon/soon, only "on".
			if yomi_type == "on'yomi" then
				require("Module:debug/track")("kanjitab/unspecified on")
			elseif yomi_type == "jūbakoyomi" then
				require("Module:debug/track")("kanjitab/jubakoyomi")
			elseif yomi_type == "yutōyomi" then
				require("Module:debug/track")("kanjitab/yutoyomi")
			end
			
			-- If the yomi requires a specific number of kanji (e.g. jūbakoyomi, yutōyomi).
			local req_kanji = yomi.required_kanji
			if req_kanji and #kanji ~= req_kanji then
				error("The yomi type \"" .. yomi.type .. "\" is only applicable to terms with " .. req_kanji .. " kanji.")
			elseif yomi.type == "none" then
				missing_yomi = true
			end
			
			-- Insert yomi data for each applicable kanji. Wrap in a table first, as the range for this input yomi is determined by its identity, so that (e.g.) "kun,kun" is still treated as two separate inputs.
			yomi = {data = yomi}
			for _ = 1, len do
				insert(all_yomi, yomi)
			end
		end
		-- If there are any yomi slots left, handle them as empty.
		if #all_yomi < #kanji then
			missing_yomi = true
			for _ = #all_yomi + 1, #kanji do
				insert(all_yomi, {data = yomi_data.none})
			end
		end
	elseif #kanji > 0 then
		missing_yomi = true
	end
	
	if missing_yomi then
		insert(categories, lang_name .. " terms with missing yomi")
	end
	
	-- process readings
	local readings = {}
	local readings_actual = {}
	local reading_length_total = 0
	for i = 1, args[1].maxindex do
		local reading_kana, reading_length = match(args[1][i] or "", "^(%D*)(%d*)$")
		reading_kana = reading_kana ~= "" and reading_kana or nil
		reading_length = reading_kana and tonumber(reading_length) or 1

		insert(readings, {reading_kana, reading_length})
		reading_length_total = reading_length_total + reading_length
	end
	if reading_length_total > #kanji then
		error("Readings for " .. reading_length_total .. " kanji are given, but this word has only " .. #kanji .. " kanji.")
	else
		for _ = reading_length_total + 1, #kanji do
			insert(readings, {nil, 1})
		end
	end

	local table_head = [=[
{| class="wikitable kanji-table floatright" style="text-align: center; ]=] .. (args.clearright and " clear:right;" or "") .. [=["
! ]=] .. (#kanji > 1 and "colspan=\"" .. #kanji .. "\" " or "") .. [=[style="font-weight: normal;" | [[Appendix:Japanese_glossary#kanji|Kanji]] in this term
|- lang="]=] .. lang_code .. [=[" class="Jpan" style="font-size: 2em; background: white; line-height: 1em;"

]=]
	
	if args.k.maxindex and args.k.maxindex > args[1].maxindex then
		error("kanjitab/too many k")
	end
	
	if args.o.maxindex and args.o.maxindex > args[1].maxindex then
		error("kanjitab/too many o")
	end
	
	local is_ateji = {}
	if args.ateji then
		local ateji = args.ateji
		local cat_ateji = false
		if ateji == "y" then
			for i = 1, #kanji do
				is_ateji[i] = true
			end
			cat_ateji = true
		else
			for i in gsplit(ateji, ";") do
				gsub(i, "^(%d+)$", function(a)
					is_ateji[tonumber(a)] = true
					cat_ateji = true
				end)
				gsub(i, "^(%d+),(%d+)$", function (a, b)
					for j = tonumber(a), tonumber(b) do
						is_ateji[j] = true
					end
					cat_ateji = true
				end)
			end
		end
		if cat_ateji then insert(categories, lang_name .. " terms spelled with ateji") end
	end

	-- if hiragana readings were passed,
	-- make the "spelled with ..." categories, the readings cells on the lower level and build the sort key
	-- otherwise rely on the pagename to make the original kanjitab and categories
	local cells_above = {}
	local cells_below = {}
	local kanji_pos = 1
	for i, reading in ipairs(readings) do
		local reading_kana, reading_length = reading[1], reading[2]
		local cell = {}

		if reading_length <= 1 then
			insert(cell, "| rowspan=\"2\" | ")
		else
			insert(cell, "| colspan =\"" .. reading_length .. "\" | ")
		end

		-- display reading, actual reading and okurigana
		if reading_kana then
			if reading_kana ~= "" and reading_kana ~= "-" and umatch(reading_kana, "[^" .. d_range.kana .. "]") then
				error("Please remove any non-kana characters from the reading input " .. reading_kana .. ".")
			end

			local actual_reading = args.k[i]
			local okurigana = args.o[i]

			local okurigana_text = okurigana and "(" .. okurigana .. ")" or ""
			local actual_reading_text = actual_reading and " > " .. actual_reading .. okurigana_text or ""
			local text = reading_kana .. okurigana_text .. actual_reading_text

			readings_actual[i] = {(actual_reading or reading_kana) .. (okurigana or ""), reading_length}

			insert(cell, "<span class=\"Jpan\" lang=\"" .. lang_code .. "\">" .. text .. "</span>")
			if reading_length <= 1 then insert(cell, "<br/>") end
		else
			readings_actual[i] = {nil, 1}
		end

		-- display kanji grade, categorize
		for j = kanji_pos, kanji_pos + reading_length - 1 do
			local single_kanji = kanji[j]
			local kanji_grade = m_ja.kanji_grade(single_kanji)
			local ateji_text = is_ateji[j] and "<br/><small>([[Appendix:Japanese glossary#ateji|ateji]])</small>" or ""
			
			local type, compound
			if all_yomi then
				local yomi = all_yomi[j].data
				type, compound = yomi.type, yomi.compound_reading
			end
			
			if not reading_kana then
				if type ~= "irregular" then
					require("Module:debug/track")("kanjitab/no reading")
				end
				insert(categories, lang_name .. " terms spelled with " .. single_kanji)
			elseif reading_length ~= 1 or type == "irregular" then
				insert(categories, lang_name .. " terms spelled with " .. single_kanji)
			elseif compound then
				-- Re-enable once all bad jukujikun calls are fixed.
				-- error("The yomi type \"" .. type .. "\" is only applicable to compound character readings, so cannot apply to " .. single_kanji .. " read as " .. reading_kana .. ". If this is intended as part of a " .. type .. " reading, please enter the whole reading as one, followed by the number of kanji it applies to.")
				require("Module:debug/track")("kanjitab/single kanji with jukujikun")
			else -- Subcategorize by reading.
				insert(categories, lang_name .. " terms spelled with " .. single_kanji .. " read as " .. kata_to_hira(reading_kana))
			end

			if reading_length <= 1 then
				insert(cell, "<small>" .. kanji_grade_links[kanji_grade] .. "</small>" .. ateji_text)
			else
				insert(cells_below, "| <small>" .. kanji_grade_links[kanji_grade] .. "</small>" .. ateji_text)
			end
		end
		insert(cells_above, concat(cell))
		kanji_pos = kanji_pos + reading_length
	end
	insert(cells, "|- style=\"background: white;\"")
	if #cells_below > 0 then
		insert(cells, concat(cells_above, "\n"))
		insert(cells, "|- style=\"background: white;\"")
		insert(cells, concat(cells_below, "\n"))
	else
		for i, v in ipairs(cells_above) do
			cells_above[i] = gsub(v, "| rowspan=\"2\" | ", "| ")
		end
		insert(cells, concat(cells_above, "\n"))
	end
	
	local rendaku = args.r
	if rendaku then
		insert(categories, lang_name .. " terms with rendaku")
	end
	
	if all_yomi then
		insert(cells, "|-")
		local len, all_on, yomi_cat = 1, true
		for i, yomi in ipairs(all_yomi) do
			-- If the next kanji has the same yomi table, it's part of the same range.
			if yomi == all_yomi[i + 1] then
				len = len + 1
			else
				yomi = yomi.data
				local yomi_type = yomi.type
				local display = yomi.display or yomi_type
				local appendix = yomi.appendix
				insert(cells, "| colspan=\"" .. len .. "\" |" .. (
					appendix == false and display or
					"[[Appendix:Japanese_glossary#" .. (appendix or yomi_type) .. "|" .. display .. "]]"
				))
				-- Categorise as irregular if any irregular yomi are found; otherwise, categorise if all yomi are of the same type. If yomi are of different types but are all on, on'yomi is used as a fallback.
				if yomi_cat ~= "irregular" then
					local cat_type = yomi_type
					if cat_type == "irregular" or yomi_cat == nil then
						yomi_cat = cat_type
					elseif yomi_cat ~= cat_type then
						yomi_cat = false
					end
					if not yomi.onyomi then
						all_on = false
					end
				end
				len = 1
			end
		end
		if yomi_cat then
			-- Check yomi_data first, in case cat_type is "irregular"; if no match, must be some other type, so get it from the first yomi in all_yomi, since not all yomi types are yomi_data keys.
			yomi_cat = yomi_data[yomi_cat] or all_yomi[1].data
		elseif all_on then
			yomi_cat = yomi_data.on
		elseif #all_yomi == 2 then
			local y1, y2 = all_yomi[1].data, all_yomi[2].data
			if ulen(pagename) == 2 then
				if y1.onyomi and y2.type == "kun'yomi" then
					yomi_cat = yomi_data.j -- jūbakoyomi
				elseif y1.type == "kun'yomi" and y2.onyomi then
					yomi_cat = yomi_data.y -- yutōyomi
				end
			end
		end
		if yomi_cat then
			local category = yomi_cat.reading_category
			if category ~= false then
				insert(categories, lang_name .. " " .. "terms read with " .. (category or yomi_cat.type))
			end
		end
	end

	local kanji_table
	if #kanji > 0 then
		kanji_table = table_head
		for _, v in ipairs(kanji) do
			kanji_table = kanji_table .. "| style=\"padding: 0.5em;\" | [[" .. v .. "#" .. lang_name .. "|" .. v .. "]]\n"
		end
		kanji_table = kanji_table .. concat(cells, "\n") .. "\n|}"
	else
		kanji_table = ""
	end

	local forms_table = ""
	if args.alt == "" or args.alt == "-" then args.alt = nil end
	if kyu[1] or args.alt then
		local forms = {}

		-- |kyu=
		if kyu[1] == "which 弁?" then
			insert(forms, "<strong class=\"error\" style=\"font-size:75%;\">Please specify the correct kyujitai for 弁 with the parameter \"kyu\".</strong>[[Category:Requests for cleanup in " .. lang_name .. " entries]]")
			remove(kyu, 1)
		end

		for _, form in ipairs(kyu) do
			local form_linkto, form_display = match(form, "^(.+)|(.+)$")
			if not form_linkto then form_linkto, form_display = form, form end
			insert(forms, concat{
				"<span class=\"Jpan\" lang=\"" .. lang_code .. "\" style=\"font-family:游ゴシック, HanaMinA, sans-serif; font-size:140%;\">[[",
				form_linkto,
				form_linkto == pagename and "|" or "#" .. lang_name .. "|",
				form_display,
				"]]</span> <small>",
				show_labels {labels = {"kyūjitai"}, lang = lang, nocat = true },
				"</small>",
			})
		end

		-- |alt=
		if args.alt then
			for form in gsplit(args.alt, ",") do
				local i_semicolon = find(form, ":")
				if i_semicolon then
					local altform = sub(form, 1, i_semicolon - 1)
					local altlabels = split(sub(form, i_semicolon + 1), " ")
					insert(forms, concat{
						"<span class=\"Jpan\" lang=\"" .. lang_code .. "\" style=\"font-size:140%\">[[",
						altform,
						"#" .. lang_name .. "|",
						altform,
						"]]</span> <small>",
						show_labels { labels = altlabels, lang = lang, nocat = true },
						"</small>",
					})
				else
					insert(forms, concat{
						"<span class=\"Jpan\" lang=\"" .. lang_code .. "\" style=\"font-size:140%\">[[",
						form,
						"#" .. lang_name .. "|",
						form,
						"]]</span>"
					})
				end
			end
		end

		forms_table = "\n" .. [[{| class="wikitable floatright"
! style="font-weight:normal" | Alternative spelling]] .. (#forms == 1 and "" or "s") .. [[

|-
| style="text-align:center;font-size:108%" | ]] .. concat(forms, "<br>") .. "\n|}"
	end
	
	local forms_table2 = ""
	if args.alt2 and args.alt2 ~= "" and args.alt2 ~= "-" then
		local forms2 = {}
		for form in gsplit(args.alt2, ",") do
			insert(forms2, "<span class=\"Jpan\" lang=\"" .. lang_code .. "\">[[" .. form .. "#" .. lang_name .. "|" .. form .. "]]</span>")
		end
		forms_table2 = "\n" .. [[{| class="wikitable floatright"
! style="font-weight:normal" | Variant form]] .. (#forms2 == 1 and "" or "s") .. "\n" .. [[
| style="text-align:center;font-size:140%" | ]] .. concat(forms2, "<br>") .. "\n|}"
	end

	-- use user-provided sortkey if we got one, otherwise
	-- use the sortkey we've already made by combining the
	-- readings if provided, if we have neither then
	-- default to empty string and don't sort
	local sortkey
	if args.sort then
		sortkey = args.sort
	else
		sortkey = {non_kanji[1]}
		local id = 1
		for _, v in ipairs(readings_actual) do
			id = id + v[2]
			insert(sortkey, (v[1] or "") .. (non_kanji[id] or ""))
		end
		sortkey = concat(sortkey)
	end
	if sortkey == "" then
		sortkey = nil
	else
		sortkey = lang:makeSortKey(sortkey)
	end
	
	if sortkey ~= lang:makeSortKey(PAGENAME) then
		require("Module:debug/track"){"kanjitab/nonstandard sortkey", "kanjitab/nonstandard sortkey/" .. lang_code}
	end

	return kanji_table .. forms_table .. forms_table2 .. m_utilities.format_categories(categories, lang, sortkey)
end

return export