モジュール:AgeEstimator

提供: 静かの海wiki
2026年9月28日 (月) 14:44時点におけるエーデルメーア (トーク | 投稿記録)による版 (ページの作成:「-- Module:AgeEstimator local p = {} local function trim(s) if s == nil then return '' end s = tostring(s) return mw.ustring.gsub(s, '^%s*(.-)%s*$', '%1') end -- 実質的な情報を含まない値(プレースホルダー)かどうか local function isPlaceholder(s) s = trim(s) if s == '' then return true end if mw.ustring.match(s, '^[%-_%s%.•・、。,,.-ー–—// ]+$') then return true end local lower =…」)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)
ナビゲーションに移動 検索に移動

このモジュールについての説明文ページを モジュール:AgeEstimator/doc に作成できます

-- Module:AgeEstimator
local p = {}

local function trim(s)
    if s == nil then return '' end
    s = tostring(s)
    return mw.ustring.gsub(s, '^%s*(.-)%s*$', '%1')
end

-- 実質的な情報を含まない値(プレースホルダー)かどうか
local function isPlaceholder(s)
    s = trim(s)
    if s == '' then return true end
    if mw.ustring.match(s, '^[%-_%s%.•・、。,,.-ー–—// ]+$') then
        return true
    end
    local lower = mw.ustring.lower(s)
    local nolist = {'不明','なし','無し','未知','未定','未詳','不詳',
                    'unknown','n/a','none','tbd','null','nil'}
    for _, w in ipairs(nolist) do
        if lower == w then return true end
    end
    return false
end

local APPROX = {
    '前後','頃','ごろ','くらい','ぐらい','弱','強','約',
    'およそ','大体','ほぼ','辺り','あたり','付近',
    '大约','约','左右','前后','上下',
    '약','정도','쯤','경',
    'примерно','около','приблизительно','почти','с лишним',
    'approximately','around','about','circa','-ish','or so',
    'حوالي','نحو','قرابة','تقريبا',
}
local EARLY = {
    '初め','初期','初頭','前半','上期','上半期','上半','前段','前叶','前葉',
    '초반','초기','상반기',
    'начало','начале','первая','первой','ранн',
    'early','first','1st',
    'المبكر','الأول',
}
local MID = {
    '中頃','中ごろ','半ば','中盤','中期','中葉',
    '중반','중기',
    'середина','середине','средн',
    'mid','middle',
    'الأوسط','منتصف',
}
local LATE = {
    '終わり','末期','終期','晩期','最末期','末','後半','下期','下半期','下半','後期',
    '后期','末期','末','后半',
    '후반','후기','하반기',
    'конец','конце','конца','поздн','последн','вторая','второй',
    'late','last','second','2nd',
    'المتأخر','الأخير','الثاني',
}

local MONTH_IN = {
    january=1, february=2, march=3, april=4, may=5, june=6,
    july=7, august=8, september=9, october=10, november=11, december=12,
    jan=1, feb=2, mar=3, apr=4, jun=6, jul=7, aug=8,
    sep=9, sept=9, oct=10, nov=11, dec=12,
    ['января']=1, ['февраля']=2, ['марта']=3, ['апреля']=4,
    ['мая']=5, ['июня']=6, ['июля']=7, ['августа']=8,
    ['сентября']=9, ['октября']=10, ['ноября']=11, ['декабря']=12,
    ['январь']=1, ['февраль']=2, ['март']=3, ['апрель']=4,
    ['май']=5, ['июнь']=6, ['июль']=7, ['август']=8,
    ['сентябрь']=9, ['октябрь']=10, ['ноябрь']=11, ['декабрь']=12,
    ['يناير']=1, ['فبراير']=2, ['مارس']=3, ['أبريل']=4, ['ابريل']=4,
    ['مايو']=5, ['يونيو']=6, ['يوليو']=7,
    ['أغسطس']=8, ['اغسطس']=8,
    ['سبتمبر']=9, ['أكتوبر']=10, ['اكتوبر']=10,
    ['نوفمبر']=11, ['ديسمبر']=12,
}

local MONTH_OUT = {
    en = {'January','February','March','April','May','June',
          'July','August','September','October','November','December'},
    ru = {'января','февраля','марта','апреля','мая','июня',
          'июля','августа','сентября','октября','ноября','декабря'},
    ar = {'يناير','فبراير','مارس','أبريل','مايو','يونيو',
          'يوليو','أغسطس','سبتمبر','أكتوبر','نوفمبر','ديسمبر'},
}

local function containsAny(s, list)
    for _, w in ipairs(list) do
        if mw.ustring.find(s, w, 1, true) then return true end
    end
    return false
end

local function findMonthDayByName(s)
    local lower_s = mw.ustring.lower(s)
    for name, num in pairs(MONTH_IN) do
        if mw.ustring.find(lower_s, name, 1, true) then
            local d = mw.ustring.match(lower_s, '(%d%d?)%s*' .. name)
            if not d then
                d = mw.ustring.match(lower_s, name .. '%s*(%d%d?)')
            end
            if d then
                return num, tonumber(d)
            end
        end
    end
    return nil
end

local function normalize(s)
    s = tostring(s or '')
    s = mw.ustring.gsub(s, '[??]', '')
    s = mw.ustring.gsub(s, '[0-9]', function(c)
        return string.char(string.byte(c) - 0xFEE0)
    end)
    local map = {
        ['٠']='0',['١']='1',['٢']='2',['٣']='3',['٤']='4',
        ['٥']='5',['٦']='6',['٧']='7',['٨']='8',['٩']='9',
        ['۰']='0',['۱']='1',['۲']='2',['۳']='3',['۴']='4',
        ['۵']='5',['۶']='6',['۷']='7',['۸']='8',['۹']='9',
    }
    s = mw.ustring.gsub(s, '[٠-٩۰-۹]', function(c) return map[c] end)
    s = mw.ustring.gsub(s, ' ', ' ')
    s = mw.ustring.gsub(s, '[ー-−―─—–~~〜]', '-')
    s = mw.ustring.lower(s)
    s = mw.ustring.gsub(s, '%s*から%s*', '-')
    s = mw.ustring.gsub(s, '%s*まで%s*', '-')
    s = mw.ustring.gsub(s, '%s*至%s*', '-')
    s = mw.ustring.gsub(s, '%s*到%s*', '-')
    s = mw.ustring.gsub(s, '%s*[Tt][Oo]%s*', '-')
    s = mw.ustring.gsub(s, '%s+по%s+', '-')
    s = mw.ustring.gsub(s, '%s*[Bb][Ii][Ss]%s+', '-')
    s = mw.ustring.gsub(s, '^%s*[Ff][Rr][Oo][Mm]%s+', '')
    s = mw.ustring.gsub(s, '^%s*[Bb][Oo][Rr][Nn]%s+(in%s+|on%s+)?', '')
    s = mw.ustring.gsub(s, '^%s*[Dd][Ii][Ee][Dd]%s+(in%s+|on%s+)?', '')
    s = mw.ustring.gsub(s, '^%s*[Dd][Ee]%s+', '')
    s = mw.ustring.gsub(s, '^%s*[Cc][Ii][Rr][Cc][Aa]%s+', '')
    s = mw.ustring.gsub(s, '^%s*[Cc][Aa]%.?%s+', '')
    s = mw.ustring.gsub(s, '^%s*с%s+', '')
    s = mw.ustring.gsub(s, '^%s*от%s+', '')
    return mw.text.trim(s)
end

local function extractNumbers(s)
    local nums = {}
    for n in mw.ustring.gmatch(s, '%d+') do
        table.insert(nums, tonumber(n))
    end
    return nums
end

local function isApproximate(s)
    if containsAny(s, APPROX) then return true end
    if mw.ustring.find(s, '-', 1, true) then return true end
    return false
end

-- 元号(令和の終端は現在年)
local CURRENT_YEAR = tonumber(os.date('%Y')) or 2026
local ERAS = {
    {'明治', 1868, 1912},
    {'大正', 1912, 1926},
    {'昭和', 1926, 1989},
    {'平成', 1989, 2019},
    {'令和', 2019, CURRENT_YEAR},
}

local function parseEra(s)
    for _, era in ipairs(ERAS) do
        local name, S, E = era[1], era[2], era[3]
        if mw.ustring.find(s, name, 1, true) then
            local L = E - S
            local n = tonumber(mw.ustring.match(s, '(%d+)年'))
            if n and n < 100 then
                return {year = S + n - 1, exact = false}
            end
            local dec = tonumber(mw.ustring.match(s, '(%d+)年代'))
            if dec and dec < 100 then
                return {year = S + dec - 1 + 5, exact = false}
            end
            if containsAny(s, {'末期','終期','晩期','最末期'}) then
                return {year = E - L/12, exact = false}
            end
            if containsAny(s, EARLY) then
                return {year = S + L/6, exact = false}
            end
            if containsAny(s, MID) then
                return {year = S + L/2, exact = false}
            end
            if containsAny(s, LATE) then
                return {year = S + 5*L/6, exact = false}
            end
            return {year = S + L/2, exact = false}
        end
    end
    return nil
end

local function parseYMD(s)
    local y1,m1,d1,y2,m2,d2 = mw.ustring.match(
        s, '(%d%d%d%d)年(%d%d?)月(%d%d?)日.-(%d%d%d%d)年(%d%d?)月(%d%d?)日')
    if y1 then
        return {year=(tonumber(y1)+tonumber(y2))/2,
                month=(tonumber(m1)+tonumber(m2))/2,
                day=(tonumber(d1)+tonumber(d2))/2, exact=false}
    end
    local y3,m3,d3,d4 = mw.ustring.match(
        s, '(%d%d%d%d)年(%d%d?)月(%d%d?)日.-(%d%d?)日')
    if y3 then
        return {year=tonumber(y3), month=tonumber(m3),
                day=(tonumber(d3)+tonumber(d4))/2, exact=false}
    end
    local y4,m4,d4b = mw.ustring.match(s, '(%d%d%d%d)年(%d%d?)月(%d%d?)日')
    if y4 then return {year=tonumber(y4),month=tonumber(m4),day=tonumber(d4b),exact=true} end
    local yk1,mk1,dk1,yk2,mk2,dk2 = mw.ustring.match(
        s, '(%d%d%d%d)년%s*(%d%d?)월%s*(%d%d?)일.-(%d%d%d%d)년%s*(%d%d?)월%s*(%d%d?)일')
    if yk1 then
        return {year=(tonumber(yk1)+tonumber(yk2))/2,
                month=(tonumber(mk1)+tonumber(mk2))/2,
                day=(tonumber(dk1)+tonumber(dk2))/2, exact=false}
    end
    local yk,mk,dk = mw.ustring.match(s, '(%d%d%d%d)년%s*(%d%d?)월%s*(%d%d?)일')
    if yk then return {year=tonumber(yk),month=tonumber(mk),day=tonumber(dk),exact=true} end
    local ya, ma, da = mw.ustring.match(s, '(%d%d%d%d)[%-/%.](%d%d?)[%-/%.](%d%d?)')
    if ya then return {year=tonumber(ya),month=tonumber(ma),day=tonumber(da),exact=true} end
    local y_named = tonumber(mw.ustring.match(s, '(%d%d%d%d)'))
    if y_named then
        local mn, dd = findMonthDayByName(s)
        if mn and dd then
            return {year=y_named, month=mn, day=dd, exact=true}
        end
    end
    local y5,m5 = mw.ustring.match(s, '(%d%d%d%d)年(%d%d?)月')
    if y5 then return {year=tonumber(y5),month=tonumber(m5),day=15,exact=false} end
    local y5k,m5k = mw.ustring.match(s, '(%d%d%d%d)년%s*(%d%d?)월')
    if y5k then return {year=tonumber(y5k),month=tonumber(m5k),day=15,exact=false} end
    local y6 = tonumber(mw.ustring.match(s, '(%d%d%d%d)年'))
    if y6 then
        if containsAny(s, {'上半期','上期','上半年','上半','상반기',
                           'первая','первой','first half','1st half','النصف الأول'}) then
            return {year=y6,month=3,day=15,exact=false}
        end
        if containsAny(s, {'下半期','下期','下半年','下半','하반기',
                           'вторая','второй','second half','2nd half','النصف الثاني'}) then
            return {year=y6,month=9,day=15,exact=false}
        end
        return {year=y6,exact=true}
    end
    local y6k = tonumber(mw.ustring.match(s, '(%d%d%d%d)년'))
    if y6k then
        if containsAny(s, {'상반기'}) then
            return {year=y6k,month=3,day=15,exact=false}
        end
        if containsAny(s, {'하반기'}) then
            return {year=y6k,month=9,day=15,exact=false}
        end
        return {year=y6k,exact=true}
    end
    return nil
end

local function parseDecade(s, nums)
    local era = parseEra(s)
    if era then return era end

    if #nums == 0 then return nil end
    local off = 5
    if containsAny(s, EARLY) then off = 2
    elseif containsAny(s, MID) then off = 5
    elseif containsAny(s, LATE)  then off = 7 end

    local base4 = mw.ustring.match(s, '(%d%d%d%d)s')
    if base4 then return {year = tonumber(base4) + off, exact = false} end
    local base2 = mw.ustring.match(s, '(%d%d)s')
    if base2 then return {age = tonumber(base2) + off, exact = false} end

    local nendai = mw.ustring.find(s, '年代', 1, true)
                or mw.ustring.find(s, '년대', 1, true)
    local dai = mw.ustring.find(s, '代', 1, true)
             or mw.ustring.find(s, '台', 1, true)
             or mw.ustring.find(s, '대', 1, true)

    if nendai then
        local base = nums[1]
        if base < 100 then base = base + 1900 end
        return {year = base + off, exact = false}
    end

    local ru = mw.ustring.match(s, '(%d%d%d%d)[%-]?[ех]')
    if ru then return {year = tonumber(ru) + off, exact = false} end

    if dai then
        local base = nums[1]
        if base >= 1900 then return {year = base + off, exact = false} end
        if base < 100 then return {age = base + off, exact = false} end
    end
    return nil
end

local function parseAge(s, nums)
    local markers = {'歳','才','살','세','years old','year old','y.o.','yo.',
                     ' سنة','سنةً','عاما','عاماً','عام'}
    local found = false
    for _, m in ipairs(markers) do
        if mw.ustring.find(s, m, 1, true) then found = true; break end
    end
    if not found then return nil end
    if #nums == 0 then return nil end
    if #nums >= 2 then
        local a,b = nums[1],nums[2]
        if a>b then a,b = b,a end
        return {age=(a+b)/2, exact=false}
    end
    local a = nums[1]
    if mw.ustring.find(s, '弱', 1, true) then a = a - 1 end
    if mw.ustring.find(s, '強', 1, true) then a = a + 1 end
    return {age=a, exact=not isApproximate(s)}
end

local function parseExpression(s)
    s = normalize(s)
    if s == '' then return nil end
    local nums = extractNumbers(s)
    if #nums == 0 and not mw.ustring.find(s, '昭和', 1, true)
       and not mw.ustring.find(s, '平成', 1, true)
       and not mw.ustring.find(s, '大正', 1, true)
       and not mw.ustring.find(s, '明治', 1, true)
       and not mw.ustring.find(s, '令和', 1, true) then
        return nil
    end

    local dec = parseDecade(s, nums); if dec then return dec end
    local age = parseAge(s, nums);    if age then return age end
    local ymd = parseYMD(s);          if ymd then return ymd end

    if #nums >= 2 then
        local a,b = nums[1], nums[2]
        if a >= 1000 and a <= 2200 and b < 100 then
            local mult = 10 ^ #tostring(b)
            b = math.floor(a / mult) * mult + b
        end
        if a > b then a,b = b,a end
        if a >= 1000 and b <= 2200 then
            return {year=(a+b)/2, exact=false}
        else
            return {age=(a+b)/2,  exact=false}
        end
    end
    local n = nums[1]
    if n >= 1000 and n <= 2200 then
        return {year=n, exact=not isApproximate(s)}
    else
        return {age=n,  exact=not isApproximate(s)}
    end
end

-- 月日のみを抽出(年なし)
local function parseMonthDay(s)
    if s == '' then return nil end
    local bm, bd = mw.ustring.match(s, '(%d%d?)月(%d%d?)日')
    if bm and bd then
        return {month=tonumber(bm), day=tonumber(bd), exact=true}
    end
    bm, bd = mw.ustring.match(s, '(%d%d?)월%s*(%d%d?)일')
    if bm and bd then
        return {month=tonumber(bm), day=tonumber(bd), exact=true}
    end
    bm = mw.ustring.match(s, '(%d%d?)月')
    if bm then
        return {month=tonumber(bm), day=15, exact=false}
    end
    bm = mw.ustring.match(s, '(%d%d?)월')
    if bm then
        return {month=tonumber(bm), day=15, exact=false}
    end
    local mn, dd = findMonthDayByName(s)
    if mn and dd then
        return {month=mn, day=dd, exact=true}
    end
    local nm, nd = mw.ustring.match(s, '^(%d%d?)[%-/%.](%d%d?)$')
    if nm and nd then
        return {month=tonumber(nm), day=tonumber(nd), exact=true}
    end
    return nil
end

-- 生年の末尾に付いた「年」「년」を数値のみの場合に限って除去
local function stripYearSuffix(s)
    s = trim(s)
    if s == '' then return s end
    local n = mw.ustring.match(s, '^(%d+)年$') or mw.ustring.match(s, '^(%d+)년$')
    if n then return n end
    return s
end

local function parseBirth(birth, birth2)
    birth = stripYearSuffix(birth)

    if birth ~= '' and birth2 == '' then
        return parseExpression(birth)
    end
    if birth == '' and birth2 ~= '' then
        return parseMonthDay(birth2)
    end
    if birth ~= '' and birth2 ~= '' then
        local by = tonumber(birth)
        if by and by >= 1000 and by <= 2200 then
            local bm, bd = mw.ustring.match(birth2, '(%d%d?)月(%d%d?)日')
            if bm and bd then
                return {year=by, month=tonumber(bm), day=tonumber(bd), exact=true}
            end
            bm, bd = mw.ustring.match(birth2, '(%d%d?)월%s*(%d%d?)일')
            if bm and bd then
                return {year=by, month=tonumber(bm), day=tonumber(bd), exact=true}
            end
            local bm2 = mw.ustring.match(birth2, '(%d%d?)月')
            if bm2 then
                return {year=by, month=tonumber(bm2), day=15, exact=false}
            end
            bm2 = mw.ustring.match(birth2, '(%d%d?)월')
            if bm2 then
                return {year=by, month=tonumber(bm2), day=15, exact=false}
            end
            local mn, dd = findMonthDayByName(birth2)
            if mn and dd then
                return {year=by, month=mn, day=dd, exact=true}
            end
            local nm, nd = mw.ustring.match(birth2, '^(%d%d?)[%-/%.](%d%d?)$')
            if nm and nd then
                return {year=by, month=tonumber(nm), day=tonumber(nd), exact=true}
            end
        end
        local combined
        if mw.ustring.find(birth2,'月') or mw.ustring.find(birth2,'日')
           or mw.ustring.find(birth2,'월') or mw.ustring.find(birth2,'일') then
            combined = birth .. '年' .. birth2
        else
            combined = birth .. birth2
        end
        return parseExpression(combined)
    end
    return nil
end

local function units(lang)
    if lang=='en'      then return ' years old',' approx.' end
    if lang=='ar'      then return ' سنة',' حوالي' end
    if lang=='zh'      then return '岁','左右' end
    if lang=='zh-hant' then return '歲','左右' end
    if lang=='ko'      then return '살','경' end
    if lang=='ru'      then return ' лет',' ок.' end
    return '歳','頃'
end

local function formatBirthDate(lang, y, m, d)
    if m and d then
        if lang=='ko' then
            return string.format('%d년 %d월 %d일', y, m, d)
        elseif lang=='en' then
            return MONTH_OUT.en[m] .. ' ' .. d .. ', ' .. y
        elseif lang=='ru' then
            return d .. ' ' .. MONTH_OUT.ru[m] .. ' ' .. y
        elseif lang=='ar' then
            return d .. ' ' .. MONTH_OUT.ar[m] .. ' ' .. y
        else
            return string.format('%d年%d月%d日', y, m, d)
        end
    else
        if lang=='ko' then
            return tostring(y) .. '년'
        elseif lang=='en' or lang=='ru' or lang=='ar' then
            return tostring(y)
        else
            return tostring(y) .. '年'
        end
    end
end

local function formatMonthDay(lang, m, d)
    if lang=='ko' then
        return string.format('%d월 %d일', m, d)
    elseif lang=='en' then
        return MONTH_OUT.en[m] .. ' ' .. d
    elseif lang=='ru' then
        return d .. ' ' .. MONTH_OUT.ru[m]
    elseif lang=='ar' then
        return d .. ' ' .. MONTH_OUT.ar[m]
    else
        return string.format('%d月%d日', m, d)
    end
end

function p.field(frame)
    local args = frame:getParent().args
    local ageStr = trim(args['年齢'])
    local birth  = trim(args['生年'])
    local birth2 = trim(args['生年2'])
    local lang   = trim(args['lang']); if lang=='' then lang='ja' end
    lang = mw.ustring.lower(lang)

    local ageInfo   = (ageStr ~= '') and parseExpression(ageStr) or nil
    local birthInfo = parseBirth(birth, birth2)

    local refY = tonumber(os.date('%Y'))
    local refM = tonumber(os.date('%m'))
    local refD = tonumber(os.date('%d'))

    local finalAge, ageApprox, ageRange = nil, false, nil
    local src = ageInfo or birthInfo
    if src then
        if src.age then
            finalAge = src.age
            ageApprox = not src.exact
        elseif src.year then
            finalAge = refY - src.year
            if src.month and src.day then
                if refM < src.month or (refM == src.month and refD < src.day) then
                    finalAge = finalAge - 1
                end
            end
            ageApprox = not src.exact
            if lang=='ja' and not ageInfo and src.exact
               and not src.month and src.year then
                local y = math.floor(src.year + 0.5)
                local mx = refY - y
                ageRange = {min=mx-1, max=mx}
            end
        end
    end

    local ageUnit, approxMark = units(lang)

    local birthOut = ''
    if birthInfo then
        if birthInfo.year then
            local by = math.floor(birthInfo.year + 0.5)
            local bm = birthInfo.month and math.floor(birthInfo.month + 0.5) or nil
            local bd = birthInfo.day   and math.floor(birthInfo.day   + 0.5) or nil
            birthOut = formatBirthDate(lang, by, bm, bd)
        elseif birthInfo.month and birthInfo.day then
            local bm = math.floor(birthInfo.month + 0.5)
            local bd = math.floor(birthInfo.day + 0.5)
            birthOut = formatMonthDay(lang, bm, bd)
        end
        if birthOut ~= '' and not birthInfo.exact then
            birthOut = birthOut .. approxMark
        end
    end

    if birth ~= '' and birth2 ~= '' and birthInfo and birthInfo.exact
       and not birthInfo.month then
        if lang=='en' or lang=='ar' or lang=='ru' then
            birthOut = birth2 .. ', ' .. birth
        elseif lang=='ja' or lang=='zh' or lang=='zh-hant' then
            birthOut = birth .. '年' .. birth2
        elseif lang=='ko' then
            birthOut = birth .. '년 ' .. birth2
        end
    end

    if birthOut == '' and finalAge then
        local by = math.floor(refY - finalAge + 0.5)
        if lang=='en' or lang=='ru' or lang=='ar' then
            birthOut = tostring(by)
        elseif lang=='ko' then
            birthOut = tostring(by) .. '년'
        else
            birthOut = tostring(by) .. '年'
        end
        birthOut = birthOut .. approxMark
    end

    local ageOut = ''
    if ageRange then
        ageOut = tostring(ageRange.min)..'~'..tostring(ageRange.max)..ageUnit
    elseif finalAge then
        local a = math.floor(finalAge + 0.5)
        ageOut = tostring(a)..ageUnit
        if ageApprox then ageOut = ageOut .. approxMark end
    end

    if ageInfo == nil and ageStr ~= '' and not isPlaceholder(ageStr) then
        ageOut = ageStr
    end

    if birthInfo == nil and (birth ~= '' or birth2 ~= '') then
        local combined
        if birth ~= '' and birth2 ~= '' then
            combined = birth .. ' ' .. birth2
        elseif birth ~= '' then
            combined = birth
        else
            combined = birth2
        end
        if not isPlaceholder(combined) then
            birthOut = combined
        end
    end

    if lang=='en' then
        if ageOut~='' and birthOut~='' then return ageOut..', born '..birthOut end
        if ageOut~='' then return ageOut end
        if birthOut~='' then return 'born '..birthOut end
    elseif lang=='ar' then
        if ageOut~='' and birthOut~='' then return ageOut..'، ولد في '..birthOut end
        if ageOut~='' then return ageOut end
        if birthOut~='' then return 'ولد في '..birthOut end
    elseif lang=='zh' or lang=='zh-hant' then
        if ageOut~='' and birthOut~='' then return ageOut..','..birthOut..'生' end
        if ageOut~='' then return ageOut end
        if birthOut~='' then return birthOut..'生' end
    elseif lang=='ko' then
        if ageOut~='' and birthOut~='' then return ageOut..', '..birthOut..'생' end
        if ageOut~='' then return ageOut end
        if birthOut~='' then return birthOut..'생' end
    elseif lang=='ru' then
        if ageOut~='' and birthOut~='' then return ageOut..', род. '..birthOut end
        if ageOut~='' then return ageOut end
        if birthOut~='' then return 'род. '..birthOut end
    else
        if ageOut~='' and birthOut~='' then return ageOut..'、'..birthOut..'生まれ' end
        if ageOut~='' then return ageOut end
        if birthOut~='' then return birthOut..'生まれ' end
    end
    return ''
end

function p.hasdata(frame)
    local args = frame:getParent().args
    local ageStr = trim(args['年齢'])
    local birth  = trim(args['生年'])
    local birth2 = trim(args['生年2'])

    if ageStr ~= '' and not isPlaceholder(ageStr) then return 'x' end
    if birth ~= '' and not isPlaceholder(birth) then return 'x' end
    if birth2 ~= '' and not isPlaceholder(birth2) then return 'x' end
    return ''
end

return p