ludic/selfhost/backend/emit_schema_lang.ludic

224 lines
6.5 KiB
Text

# emit_schema_lang.ludic — the schema's "lang" (emit_schema.ludic): what a language editor needs of
# the keys (i18n.ludic). null when the program's package.ludic has no `lang` line; otherwise
#
# dir, source, file the languages' directory, the source language and its .po (null if missing)
# keys every key used, sorted: its kind (code, template or data - the first site's, and
# `kinds` all of them), every site as "file:line:col", whether en.po has it, its
# English, whether it is a plural, and en.po's #. description (or null)
# unused en.po's keys used nowhere
# undescribed en.po's keys with no #. description
# split one English under several keys: [{english, keys}] - each wants a description
# languages every other .po in the directory: {code: {file, missing, fuzzy, extra}} - en.po's
# keys it lacks (or leaves empty), its fuzzy ones, and what it has that en.po does not
function sc_lang(b: Buf) -> void {
if not g_i18n_on {
buf_puts(b, "null")
return
}
let en = g_i18n_en
buf_puts(b, "{\n \"dir\": ")
jq_put(b, g_i18n_dir)
buf_puts(b, ",\n \"source\": ")
jq_put(b, g_i18n_src)
buf_puts(b, ",\n \"file\": ")
if en == null { buf_puts(b, "null") } else { jq_put(b, g_i18n_en_path) }
buf_puts(b, ",\n \"keys\": [")
let used = ss_new(1024)
sc_lang_keys(b, used)
buf_puts(b, "\n ],\n \"unused\": [")
if en != null { sc_lang_ids(b, en, used, 0) }
buf_puts(b, "],\n \"undescribed\": [")
if en != null { sc_lang_ids(b, en, used, 1) }
buf_puts(b, "],\n \"split\": [")
if en != null { sc_lang_split(b, en) }
buf_puts(b, "\n ],\n \"languages\": {")
sc_lang_others(b, en)
buf_puts(b, "\n }\n }")
}
function sc_lang_keys(b: Buf, used: StrSet) -> void {
let xs = new []int
let keys = new []pointer
var i = 0
while i < len(g_ks_key) {
push(xs, i)
push(keys, `{g_ks_key[i]}\t{g_ks_file[i]}\t{itoa(1000000 + g_ks_line[i])}\t{itoa(1000000 + g_ks_col[i])}`)
i += 1
}
sc_sort(xs, keys)
var a = 0
var first = true
while a < len(xs) {
let key = g_ks_key[xs[a]]
var z = a + 1
while z < len(xs) and (g_ks_key[xs[z]] == key) { z += 1 }
let dummy = ss_add(used, key)
sc_sep(b, first, " ")
first = false
buf_puts(b, "{\"key\": ")
jq_put(b, key)
buf_puts(b, ", \"kind\": ")
jq_put(b, i18n_ks_name(g_ks_kind[xs[a]]))
buf_puts(b, ", \"kinds\": [")
var kd = 0
var nk = 0
while kd < 3 {
var has = false
var q = a
while q < z {
if g_ks_kind[xs[q]] == kd { has = true }
q += 1
}
if has {
if nk > 0 { buf_puts(b, ", ") }
jq_put(b, i18n_ks_name(kd))
nk += 1
}
kd += 1
}
buf_puts(b, "], \"sites\": [")
var s = a
while s < z {
let j = xs[s]
if s > a { buf_puts(b, ", ") }
jq_put(b, `{g_ks_file[j]}:{itoa(g_ks_line[j])}:{itoa(g_ks_col[j])}`)
s += 1
}
buf_puts(b, "]")
let e = po_find(g_i18n_en, key)
buf_puts(b, ", \"in_source\": ")
sc_bool(b, e >= 0)
buf_puts(b, ", \"english\": ")
if e >= 0 { jq_put(b, g_i18n_en.first[e]) } else { buf_puts(b, "null") }
buf_puts(b, ", \"plural\": ")
sc_bool(b, e >= 0 and g_i18n_en.plural[e])
buf_puts(b, ", \"description\": ")
if e >= 0 { sc_str(b, g_i18n_en.desc[e]) } else { buf_puts(b, "null") }
buf_puts(b, "}")
a = z
}
}
# en.po's ids, sorted: those used nowhere (what 0), or those with no description (what 1)
function sc_lang_ids(b: Buf, en: PoFile, used: StrSet, what: int) -> void {
let xs = new []int
var i = 0
while i < len(en.ids) {
var take = false
if what == 0 { take = not ss_has(used, en.ids[i]) }
if what == 1 { take = en.desc[i] == null }
if take { push(xs, i) }
i += 1
}
sc_sort(xs, en.ids)
i = 0
while i < len(xs) {
if i > 0 { buf_puts(b, ", ") }
jq_put(b, en.ids[xs[i]])
i += 1
}
}
function sc_lang_split(b: Buf, en: PoFile) -> void {
let xs = new []int
let keys = new []pointer
var i = 0
while i < len(en.ids) {
push(xs, i)
push(keys, `{en.first[i]}\t{en.ids[i]}`)
i += 1
}
sc_sort(xs, keys)
var first = true
var a = 0
while a < len(xs) {
var z = a + 1
while z < len(xs) and (en.first[xs[z]] == en.first[xs[a]]) { z += 1 }
if z - a > 1 and len(en.first[xs[a]]) > 0 {
sc_sep(b, first, " ")
first = false
buf_puts(b, "{\"english\": ")
jq_put(b, en.first[xs[a]])
buf_puts(b, ", \"keys\": [")
var k = a
while k < z {
if k > a { buf_puts(b, ", ") }
jq_put(b, en.ids[xs[k]])
k += 1
}
buf_puts(b, "]}")
}
a = z
}
}
# every other language in the directory, against en.po
function sc_lang_others(b: Buf, en: PoFile) -> void {
let fs = pmk_list(g_i18n_dir, false)
var first = true
var i = 0
while i < len(fs) {
let f = fs[i]
let n = len(f)
if n > 3 and (f[n - 3 .. n] == ".po") and not (f[0 .. n - 3] == g_i18n_src) {
let p = po_read(`{g_i18n_dir}/{f}`)
if p != null {
sc_sep(b, first, " ")
first = false
jq_put(b, f[0 .. n - 3])
buf_puts(b, ": {\"file\": ")
jq_put(b, p.path)
buf_puts(b, ", \"missing\": [")
sc_lang_diff(b, en, p, 0)
buf_puts(b, "], \"fuzzy\": [")
sc_lang_diff(b, en, p, 1)
buf_puts(b, "], \"extra\": [")
sc_lang_diff(b, en, p, 2)
buf_puts(b, "]}")
}
}
i += 1
}
}
# 0: en.po's keys p lacks or leaves empty; 1: p's fuzzy keys; 2: p's keys en.po does not have
function sc_lang_diff(b: Buf, en: PoFile, p: PoFile, what: int) -> void {
let names = new []pointer
var i = 0
if what == 0 and en != null {
while i < len(en.ids) {
let j = po_find(p, en.ids[i])
if j < 0 or sc_lang_empty(p.strs[j]) { push(names, en.ids[i]) }
i += 1
}
}
if what == 1 {
while i < len(p.ids) {
if p.fuzzy[i] { push(names, p.ids[i]) }
i += 1
}
}
if what == 2 {
while i < len(p.ids) {
if po_find(en, p.ids[i]) < 0 { push(names, p.ids[i]) }
i += 1
}
}
let xs = new []int
i = 0
while i < len(names) {
push(xs, i)
i += 1
}
sc_sort(xs, names)
i = 0
while i < len(xs) {
if i > 0 { buf_puts(b, ", ") }
jq_put(b, names[xs[i]])
i += 1
}
}
# every form empty (the forms are joined by newlines)
function sc_lang_empty(s: pointer) -> bool {
var i = 0
while i < len(s) {
if s[i] != '\n' { return false }
i += 1
}
return true
}