Module:Tr

来自Calamity Mod Wiki
跳到导航 跳到搜索

作为 Template:tr 的补充,为 Template:tr 提供深度扩展分析辅助,以及最终的fallback。


local VariablesLua = mw.ext.VariablesLua

local database = mw.loadData( 'Module:Tr/loaddata' ) 

local main_database = database.main
local prefix_database = database.prefix
local postfix_database = database.postfix

local input_frame --保存全局输入frame

local with_of = false --全局记录输入文本是否符合 ??s of ?? 格式。

-- 所有local函数,统一定义方便递归
local stripArticle, tcBasic, procNumberPostfix, procParenPostfix, proc, tcPlural

-- 尝试检查各种可能的复数变体,匹配就tc返回,否则nil
function tcPlural(str)
	if not str then return end
	local m, n = nil, 0
	--需要特殊形态处理?: ??s of ????
	if with_of then
		m, n = str:gsub('^(.+)s( of .+)$', '%1%2')
		if n>0 then
			if main_database[m] then return main_database[m] end
			m, n = str:gsub('^(.+)es( of .+)$', '%1%2') -- -es
			if n>0 then
				if main_database[m] then return main_database[m] end
				m, n = str:gsub('^(.+)ies( of .+)$', '%1y%2') -- -ies
				if n>0 then
					if main_database[m] then return main_database[m] end
				else
					--和上一个匹配互斥,放在else可以略节约。
					m, n = str:match('^(.+)ves( of .+)$') -- -ves
					if m then
						str = m..'f'..n -- -f
						if main_database[str] then return main_database[str] end
						str = m..'fe'..n -- -fe
						if main_database[str] then return main_database[str] end
					end
				end
			end
		end
	end
	--of标记是全局的,但这里处理的还可能是其中不带of的子片段。
	if not with_of or n == 0 then
		m = str:match('^(.+)s$') -- -s
		if m then
			if main_database[m] then
				return main_database[m] 
			end
			m = m:match('^(.+)e$') -- -es
			if m then
				if main_database[m] then
					return main_database[m]
				end
				m = m:match('^(.+)i$') -- ies
				if m then
					m = m..'y'
					if main_database[m] then return main_database[m] end
				else
					--和上一个匹配互斥,放在else可以省点事
					m = str:match('^(.+)ves$') -- ves
					if m then
						m = m..'f' -- -f
						if main_database[m] then return main_database[m] end
						m = m..'e' -- -fe
						if main_database[m] then return main_database[m] end
					end
				end
			end
		end
	end
end

-- 脱冠词 成功脱掉返回脱掉后的文本,失败nil
function stripArticle(str)
	return str:match('^the%s+(.+)$') or str:match('^an?%s+(.+)$')
end

-- 基本:冠词/复数。
function tcBasic(en)
	if main_database[en] then return main_database[en] end
	local main = stripArticle(en)
	if main then
		--有冠词
		if main_database[main] then return main_database[main] end
		local zh = tcPlural(main)
		if zh then return zh end
	else
		--无冠词
		if main_database[en] then return main_database[en] end
	end
	return tcPlural(en)
end


-- 尝试去掉数字后缀,然后再次尝试做处理。
function procNumberPostfix(en)
	local main, number = en:match('^(.-)%s+([%d%.]+)$') --后缀允许数字和.混合。可能会带来少数没意义的匹配,问题不大。
	if main then
		zh = tcBasic(main) or proc(main) --主体部分还是要尝试基本款翻译,以应对 block 2 这样的情况
		if zh then
			return zh..' '..number..' ' --中文和数字之间要有空格
		end
	end
end

-- 尝试去掉数字前缀,然后再次尝试做处理。
function procNumberPrefix(en)
	local number, main = en:match('^([%d%.]+)%s+(.-)$') --前缀允许数字和.混合。可能会带来少数没意义的匹配,问题不大。
	if main then
		zh = tcBasic(main) or proc(main) --主体部分还是要尝试基本款翻译,以应对 2 block 这样的情况
		if zh then
			return ' '..number..' '..zh --中文和数字之间要有空格
		end
	end
end

-- 尝试去掉括号后缀,然后再次尝试做处理。
function procParenPostfix(en)
	local main, postfix, postfix_zh
	main, postfix = en:match('^(.-)%s*(%b())$')
	if main then
		postfix_zh = postfix_database[postfix] -- 带着括号直接看是否有专用后缀
		if not postfix_zh then
			postfix = postfix:sub(2,-2)
			postfix_zh = postfix_database[postfix]
			if not postfix_zh then
				-- 处理 (Variant 2) 这类型后缀
				local main, number = postfix:match('^(.-)%s+([%d]+)$')
				if main then
					postfix_zh = postfix_database[main] or tcBasic(main) or proc(main)
					if postfix_zh then
						postfix_zh = postfix_zh..' '..number..' ' --中文和数字之间要有空格
					end
				end
			end
			if not postfix_zh then
				postfix_zh = tcBasic(postfix) or proc(postfix)
			end
			if postfix_zh then
				postfix_zh = '('..postfix_zh..')' -- 用中文括号。
			end
		end
	end

	--加上前面主体部分
	if postfix_zh then
		local zh = tcBasic(main) or proc(main)
		if zh then return zh..postfix_zh end
	end
end

-- 复杂处理:先尝试做,/and/or分割,然后考虑数字后缀、然后尝试括号后缀、然后专用后缀/前缀、然后普通后缀/前缀。
function proc(en)
	local a, b, zh_a, zh_b, sep

	-- and/or 要考虑牛津逗号的问题,否则会导致 a, b, and c 最后被分离出“and c”片段。另外 “, and”附近的空格问题要处理。
	-- X and Y
	a, sep, b = en:match('^(.-)%s*([,%s])%s*and%s+(.+)$')
	if a then
		zh_a = tcBasic(a) or proc(a)
		if zh_a then
			zh_b = tcBasic(b) or proc(b)
			if zh_b then
				return zh_a..((sep == ',') and '、和' or '和')..zh_b
			end
		end
	end
	-- X or Y
	a, sep, b = en:match('^(.-)%s*([,%s])%s*or%s+(.+)$')
	if a then
		zh_a = tcBasic(a) or proc(a)
		if zh_a then
			zh_b = tcBasic(b) or proc(b)
			if zh_b then
				return zh_a..((sep == ',') and '、或' or '或')..zh_b
			end
		end
	end

	-- X , Y  and  X / Y
	a, sep, b = en:match('^(.-)%s*([,/])%s*(.+)$')
	if a then
		zh_a = tcBasic(a) or proc(a)
		if zh_a then
			zh_b = tcBasic(b) or proc(b)
			if zh_b then
				return zh_a..((sep == ',') and '、' or '/')..zh_b
			end
		end
	end

	local zh, postfix_main, prefix_main, postfix, prefix, postfix_zh, prefix_zh

	zh = procNumberPostfix(en) or procNumberPrefix(en) or procParenPostfix(en)
	if zh then return zh end
	
	--尝试解析后缀
	postfix_main, postfix = en:match('^(.-)%s+([%w%d]+)$')
	
	--专用后缀
	if postfix_main then
		postfix_zh = postfix_database[postfix]
		if postfix_zh then
			zh = tcBasic(postfix_main) or proc(postfix_main)
			if zh then return zh..postfix_zh end
		end
	end
	
	--尝试解析前缀:
	en = stripArticle(en) or en -- 预脱冠词,否则前缀分析没意义
	prefix, prefix_main = en:match('^(%S+)%s+(.+)$')

	-- 专用前缀
	if prefix_main then
		prefix_zh = prefix_database[prefix]
		if prefix_zh then
			zh = tcBasic(prefix_main) or proc(prefix_main)
			if zh then return prefix_zh..zh	end
		end
	end
		
	-- 普通后缀:
	if postfix_main then
		postfix_zh = tcBasic(postfix)
		if postfix_zh then
			zh = tcBasic(postfix_main) or proc(postfix_main)
			if zh then return zh..postfix_zh end
		end
	end
	
	-- 普通前缀
	if prefix_main then
		prefix_zh = tcBasic(prefix)
		if prefix_zh then
			zh = tcBasic(prefix_main) or proc(prefix_main)
			if zh then return prefix_zh..zh end
		end
	end

end

-- 主返回点
return { 
	go = function(frame)
		input_frame = frame
		local input = frame.args[1]:match("^%s*(.-)%s*$") --module内要自己trim
		local en = input:lower()
		local zh, m
		--有冠词试试不脱冠词直接测试复数,补充template内的缺失(诸如 The Axe 这种天生带 the 的专有名词)
		if en:match('^the%s+(.+)$') then
			zh = tcPlural(en)
			if zh then return zh end
		end
		-- -ves结尾的额外测试一下复数,s/es/ies在模板内已经搞过了。
		local matches = en:match('^(.+)ves$')
		if matches then
			m = stripArticle(matches) or matches -- 冠词还是要脱
			--无冠词版本这里4个其实有重复,但不影响效率。
			local t = m..'f'
			if main_database[t] then return main_database[t] end
			local t = m..'fe'
			if main_database[t] then return main_database[t] end
			local t = matches..'f'
			if main_database[t] then return main_database[t] end
			local t = matches..'fe'
			if main_database[t] then return main_database[t] end
		end

		-- 额外处理 ??s of ?? 的情况,这个在模板里没搞,需要触发一次
		if en:match('^(.+)s( of .+)$') then
			with_of = true -- 保存这个信息可以在无of的场景节约一些正则匹配成本
			zh = tcPlural(en)
			if zh then return zh end
		end


	
		-- 其他复杂变体
		zh = proc(en)
	
		if zh then
			-- 处理空格问题
			zh = zh:gsub("%s+"," ")
			zh = zh:gsub("%s*(%s*","(")
			zh = zh:gsub("%s*)%s*",")")
			zh = zh:gsub("!%s+","!")
			zh = zh:gsub("?%s+","?")
			zh = zh:match("^%s*(.-)%s*$")
			return zh
		end
	
		-- 最后的fallback
		return input
	end,
	
	-- 给 template:tr 加载数据
	loadData = function(frame)
		for k,v in pairs(database.main) do
			VariablesLua.vardefine( 'ct-'..k, v )
		end
		for k,v in pairs(database.pagename) do
			VariablesLua.vardefine( 'ctPN-'..k, v )
		end
	end,
	
	--清除数据缓存
	purge = function()
		mw.ext.LuaCache.delete( 'tr__database')
	end

}