Files
triliumnext-korean-patch/updates/2026-09-05/work/trilium/build-phase9.mjs
T

47 lines
10 KiB
JavaScript

import fs from 'node:fs';import crypto from 'node:crypto';import path from 'node:path';
const b='work/trilium',out=b+'/phase9',hash=s=>crypto.createHash('sha256').update(s).digest('hex');
const apiDir=out+'/doc_notes/en/api-ko';fs.mkdirSync(apiDir,{recursive:true});
const dist=JSON.parse(fs.readFileSync(b+'/api-source/swagger-package-metadata.json')).dist;
if('sha512-'+crypto.createHash('sha512').update(fs.readFileSync(b+'/api-source/swagger-ui-dist-5.21.0.tgz')).digest('base64')!==dist.integrity)throw Error('Swagger integrity mismatch');
fs.mkdirSync(apiDir+'/swagger',{recursive:true});
for(const n of ['swagger-ui-bundle.js','swagger-ui-standalone-preset.js','swagger-ui.css','oauth2-redirect.html','favicon-16x16.png','favicon-32x32.png','LICENSE','NOTICE','package.json'])fs.copyFileSync(b+'/api-source/swagger-package/package/'+n,apiDir+'/swagger/'+n);
const reports={};
for(const[name,file]of [['internal','trilium-openapi.json'],['etapi','etapi.json']]){
const source=JSON.parse(fs.readFileSync(b+'/api-source/'+file));
const strings=JSON.parse(fs.readFileSync(b+'/api-source/'+name+'-strings.json'));
const ko=fs.readFileSync(b+'/api-'+name+'-ko.txt','utf8').trimEnd().split(/\r?\n/).map(s=>s.replaceAll('\\n','\n'));
if(strings.length!==ko.length)throw Error('Translation count '+name);
const map=new Map(strings.map((s,i)=>[s,ko[i]]));let count=0;
function walk(o){for(const[k,v]of Object.entries(o)){if(['summary','description','title'].includes(k)&&typeof v==='string'){if(!map.has(v))throw Error(v);o[k]=map.get(v);count++;}else if(v&&typeof v==='object')walk(v);}}
walk(source);
const examples={"Note 'evnnmvHTCgIn' is protected and cannot be modified through ETAPI":"노트 'evnnmvHTCgIn'은 보호되어 있어 ETAPI로 변경할 수 없습니다.","Non-matching sync versions, local is version ${server syncVersion}, remote is ${requested syncVersion}. It is recommended to run same version of Trilium on both sides of sync":"동기화 버전이 다릅니다. 로컬 버전은 ${server syncVersion}이며 원격 버전은 ${requested syncVersion}입니다. 양쪽에서 같은 Trilium 버전을 사용하는 것이 좋습니다.","Auth request time is out of sync, please check that both client and server have correct time. The difference between clocks has to be smaller than 5 minutes":"인증 요청 시각이 맞지 않습니다. 클라이언트와 서버의 시각이 올바른지 확인하세요. 두 시계의 차이는 5분 미만이어야 합니다.","TriliumNext issue tracker":"TriliumNext 문제 신고"};
function extra(o){for(const[k,v]of Object.entries(o))if(typeof v==='string'&&Object.hasOwn(examples,v))o[k]=examples[v];else if(v&&typeof v==='object')extra(v);}extra(source);
if(name==='etapi')source.servers=[{url:'/etapi',description:'현재 Trilium 서버'}];
fs.writeFileSync(apiDir+'/'+name+'.json',JSON.stringify(source,null,2));reports[name]={fields:count,unique:map.size,paths:Object.keys(source.paths).length};
}
const uiMap={'Authorize':'인증 설정','Authorized':'인증됨','Authorization':'인증','Logout':'인증 해제','Close':'닫기','Cancel':'취소','Execute':'실행','Clear':'지우기','Try it out':'직접 실행','Parameters':'매개변수','No parameters':'매개변수 없음','Request body':'요청 본문','Responses':'응답','Server response':'서버 응답','Response body':'응답 본문','Response headers':'응답 헤더','Request URL':'요청 주소','Response content type':'응답 콘텐츠 유형','Example Value':'예시 값','Schema':'스키마','Schemas':'스키마 목록','Models':'모델 목록','Model':'모델','Servers':'서버','Available authorizations':'사용 가능한 인증 방식','Name':'이름','Description':'설명','Code':'상태 코드','Details':'상세 정보','Links':'링크','No links':'링크 없음','Value':'값','required':'필수','Required':'필수','Optional':'선택 사항','Example':'예시','Examples':'예시 목록','Download':'내려받기','Download file':'파일 내려받기','Explore':'열기','Filter by tag':'태그로 필터링','Select a definition':'명세 선택','Select a spec':'명세 선택','Username:':'사용자 이름:','Password:':'비밀번호:','Value:':'값:','Name:':'이름:','In:':'전달 위치:','Username':'사용자 이름','Password':'비밀번호','Default':'기본값','default':'기본값','No content':'내용 없음','Loading...':'불러오는 중…','Loading':'불러오는 중','Errors':'오류','Hide':'숨기기','Show':'표시','Expand operation':'항목 펼치기','Collapse operation':'항목 접기','Copy to clipboard':'클립보드에 복사','Copy':'복사','Copied':'복사됨','Send empty value':'빈 값 보내기','Edit Value':'값 편집','Reset':'초기화','Available values':'사용 가능한 값','No results found':'결과 없음','Failed to fetch':'요청 실패','Undocumented':'명세에 없는 응답'};
Object.assign(uiMap,{'Examples:':'예시:','Default value':'기본값','Media type':'콘텐츠 유형','Media Type':'콘텐츠 유형','Controls':'설정할 대상:','header.':'헤더','* required':'* 필수','authorization button unlocked':'인증 설정 안 됨','authorization button locked':'인증 설정됨','List':'목록','Download API definition':'API 명세 내려받기'});
const ui=`(()=>{const dict=${JSON.stringify(uiMap)};function tr(s){let k=s.trim();if(k.endsWith(' - Website'))return s.replace(' - Website',' 웹사이트');if(k.startsWith('Send email to '))return s.replace('Send email to ','이메일 보내기: ');return Object.hasOwn(dict,k)?s.replace(k,dict[k]):s;}function update(){const root=document.querySelector('#swagger-ui');if(!root)return;const w=document.createTreeWalker(root,NodeFilter.SHOW_TEXT);for(let n;n=w.nextNode();){if(n.parentElement.closest('pre,code,textarea,script,style,.parameter__name,.model-title,.opblock-summary-path')&&!['required','* required'].includes(n.nodeValue.trim()))continue;const s=tr(n.nodeValue);if(s!==n.nodeValue)n.nodeValue=s;}for(const el of root.querySelectorAll('[title],[aria-label],[placeholder]'))for(const a of ['title','aria-label','placeholder'])if(el.hasAttribute(a)){const s=el.getAttribute(a),v=tr(s);if(v!==s)el.setAttribute(a,v);}}let queued=false;new MutationObserver(()=>{if(queued)return;queued=true;requestAnimationFrame(()=>{queued=false;update();});}).observe(document.body,{childList:true,subtree:true,characterData:true});update();document.documentElement.lang='ko';})();`;
fs.writeFileSync(out+'/swagger-ko.js',ui);
const ids=[['_help_Q2z6av6JZVWm','frontend','https://triliumnext.github.io/Notes/Script%20API/interfaces/Frontend_Script_API.Api.html'],['_help_habiZ3HU8Kw8','fnote','https://triliumnext.github.io/Notes/Script%20API/classes/Frontend_Script_API.FNote.html'],['_help_MEtfsqa5VwNi','backend','https://triliumnext.github.io/Notes/Script%20API/interfaces/Backend_Script_API.Api.html']];
const attrs=ids.map(([id,name,old])=>({attributeId:id+'_lwebViewSrc',noteId:id,oldValue:old,value:'/assets/v0.95.0-ko-20260913-r8/doc_notes/en/api-ko/'+name+'.html'}));
fs.writeFileSync(out+'/korean-phase9-pending.json',JSON.stringify(attrs,null,2));
const metaPath='doc_notes/en/User Guide/!!!meta.json';let meta=JSON.parse(fs.readFileSync(b+'/phase3/'+metaPath));
function wm(items){for(const n of items){let item=attrs.find(x=>x.noteId===n.id);if(item)for(const a of n.attributes)if(a.name==='webViewSrc')a.value=item.value;wm(n.children||[]);}}wm(meta);
fs.mkdirSync(path.dirname(out+'/'+metaPath),{recursive:true});fs.writeFileSync(out+'/'+metaPath,JSON.stringify(meta,null,2));
let main=fs.readFileSync(b+'/phase8/main.cjs','utf8');const oldmain=main;
function replace(a,c){if(main.split(a).length!==2)throw Error('Patch point '+a);main=main.replace(a,c);}
replace('jg=`assets/v${CR.version}-ko-20260913-r7`','jg=`assets/v${CR.version}-ko-20260913-r8`');
replace('var Y7s=function(){if(typeof D8e<"u"&&D8e.exports)return require("path").resolve(__dirname);','var Y7s=function(){if(typeof D8e<"u"&&D8e.exports)return "/usr/src/app/assets/doc_notes/en/api-ko/swagger";');
const old='function WEt(e){let t=eun.load((0,$Et.readFileSync)((0,GEt.join)(wp,"etapi.openapi.yaml"),"utf8")),r=JSON.parse((0,$Et.readFileSync)((0,GEt.join)(wp,"openapi.json"),"utf-8"));';
replace(old,'function WEt(e){let t=JSON.parse(require("fs").readFileSync("/usr/src/app/assets/doc_notes/en/api-ko/etapi.json","utf8")),r=JSON.parse(require("fs").readFileSync("/usr/src/app/assets/doc_notes/en/api-ko/internal.json","utf8"));');
replace('customSiteTitle:"TriliumNext ETAPI Documentation"','customSiteTitle:"TriliumNext 외부 API 참고 자료",customJsStr:'+JSON.stringify(ui));
replace('customSiteTitle:"TriliumNext Internal API Documentation"','customSiteTitle:"TriliumNext 내부 API 참고 자료",customJsStr:'+JSON.stringify(ui));
replace('W7s=Qon.default.join(wp,"etapi.openapi.yaml")','W7s=Qon.default.join(wp,"doc_notes/en/api-ko/etapi.json")');
replace('triliumApplyKoreanPdf()})','triliumApplyKoreanPdf();triliumApplyKoreanApiLinks()})');
const hook=`\nfunction triliumApplyKoreanApiLinks(){const fs=require('fs'),file='/home/node/trilium-data/korean-phase9-pending.json';if(!fs.existsSync(file))return;const rows=JSON.parse(fs.readFileSync(file,'utf8'));if(rows.length!==3)throw Error('Invalid API link plan');de.transactional(()=>{for(const r of rows){if(!['_help_Q2z6av6JZVWm','_help_habiZ3HU8Kw8','_help_MEtfsqa5VwNi'].includes(r.noteId))throw Error('API note outside scope');const a=J.getAttributeOrThrow(r.attributeId);if(a.isDeleted||a.noteId!==r.noteId||a.name!=='webViewSrc'||(a.value!==r.oldValue&&a.value!==r.value))throw Error('API link conflict');}for(const r of rows){const a=J.getAttributeOrThrow(r.attributeId);if(a.value!==r.value){a.value=r.value;a.save();}}});fs.renameSync(file,file+'.applied-'+Date.now());console.log('KOREAN_API_LINKS 3');}\n`;
fs.writeFileSync(out+'/main.cjs',hook+main);
const files=Object.fromEntries(fs.readdirSync(out,{recursive:true}).filter(n=>fs.statSync(out+'/'+n).isFile()&&!['manifest.json','api-audit.json'].includes(n)).map(n=>[n.replaceAll('\\','/'),hash(fs.readFileSync(out+'/'+n))]));
fs.writeFileSync(out+'/manifest.json',JSON.stringify({previousMainSha256:hash(oldmain),previousMetaSha256:hash(fs.readFileSync(b+'/phase3/'+metaPath)),files},null,2));
fs.writeFileSync(out+'/api-audit.json',JSON.stringify(reports,null,2));console.log(reports);