이미지 206개 한국어 설명과 예제 대시보드 및 화면 문구 보완
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import fs from 'node:fs';import crypto from 'node:crypto';
|
||||
const b='work/trilium',out=b+'/phase7';fs.mkdirSync(out,{recursive:true});
|
||||
const input=(process.env.TRILIUM_PHASE7_INPUT||'work/trilium-gitea/updates/2026-09-05/phase7-input')+'/layout_commons.js';
|
||||
let ui=fs.readFileSync(input,'utf8');const oldui=ui;
|
||||
const replacements=[
|
||||
['title:"Wrap text"','title:"글자 둘러싸기"'],
|
||||
['Related settings','관련 설정'],
|
||||
['Color scheme for code blocks in text notes','텍스트 노트의 코드 블록 색상'],
|
||||
['Color scheme for code notes','코드 노트 색상'],
|
||||
['message:"Geolocation not supported."','message:"위치 정보를 지원하지 않습니다."'],
|
||||
['f=r.message||(d===1?"permission denied":d===2?"position unavailable":"timeout")','f=d===1?"위치 정보 접근 권한이 없습니다":d===2?"위치를 확인할 수 없습니다":d===3?"위치 확인 시간이 초과되었습니다":(r.message||"위치를 확인하지 못했습니다")'],
|
||||
['message:"Geolocation error: "','message:"위치 정보 오류: "'],
|
||||
['id:"geo-new-note",title:"New note"','id:"geo-new-note",title:"새 노트"'],
|
||||
['label:"Zoom"','label:"확대 비율"'],
|
||||
['label:"New geo note"','label:"지도에 새 노트 추가"'],
|
||||
['pt.post("llm/chat",{title:"Note Chat"','pt.post("llm/chat",{title:"노트와 대화"']
|
||||
];
|
||||
for(const[from,to]of replacements){if(!ui.includes(from))throw Error('Patch point missing '+from);ui=ui.replaceAll(from,to);}
|
||||
fs.writeFileSync(out+'/layout_commons.js',ui);
|
||||
let main=fs.readFileSync(b+'/phase6/main.cjs','utf8');const oldmain=main;
|
||||
const prefix='jg=`assets/v${CR.version}-ko-20260913-r5`';if(main.split(prefix).length!==2)throw Error('Cache prefix mismatch');
|
||||
main=main.replace(prefix,'jg=`assets/v${CR.version}-ko-20260913-r6`');fs.writeFileSync(out+'/main.cjs',main);
|
||||
const hash=s=>crypto.createHash('sha256').update(s).digest('hex');
|
||||
fs.writeFileSync(out+'/manifest.json',JSON.stringify({previousMainSha256:hash(oldmain),previousLayoutSha256:hash(oldui),files:Object.fromEntries(['main.cjs','layout_commons.js'].map(n=>[n,hash(fs.readFileSync(out+'/'+n))]))},null,2));
|
||||
fs.writeFileSync(out+'/changes.json',JSON.stringify(replacements,null,2));console.log({patchRules:replacements.length});
|
||||
@@ -0,0 +1,77 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import crypto from 'node:crypto';
|
||||
const b='work/trilium',src=b+'/phase3/doc_notes/en',out=b+'/phase8';
|
||||
fs.mkdirSync(out,{recursive:true});
|
||||
const hash=s=>crypto.createHash('sha256').update(s).digest('hex');
|
||||
const files={},previous={},audit=[];
|
||||
function save(rel,old,value){const dest=out+'/'+rel;fs.mkdirSync(path.dirname(dest),{recursive:true});fs.writeFileSync(dest,value);files[rel]=hash(value);previous[rel]=hash(old);}
|
||||
const en=JSON.parse(fs.readFileSync(b+'/scoped/translation.en.json')),ko=JSON.parse(fs.readFileSync(b+'/phase6/translation.json'));
|
||||
const glossary=new Map();
|
||||
function walk(a,c){for(const[k,v]of Object.entries(a)){if(typeof v==='object'&&v)walk(v,c?.[k]||{});else if(typeof v==='string'&&typeof c?.[k]==='string'&&/^[A-Za-z][A-Za-z /-]{2,48}$/.test(v)&&/[가-힣]/.test(c[k])&&!/[<>{}]/.test(c[k]))glossary.set(v,c[k]);}}
|
||||
walk(en,ko);
|
||||
const dashboardMap={
|
||||
'🏠 Trilium Overview':'🏠 Trilium 개요','Current Trilium version and build information':'현재 Trilium 버전과 빌드 정보','📋 Instance Information':'📋 실행 환경 정보','Database file size in human-readable format':'읽기 쉬운 단위로 표시한 데이터베이스 파일 크기','💾 Database Size':'💾 데이터베이스 크기','Total active notes in your Trilium instance':'현재 Trilium에서 사용 중인 전체 노트 수','📝 Active Notes':'📝 사용 중인 노트','📊 Key Metrics':'📊 주요 지표','Total notes including deleted ones':'삭제된 노트를 포함한 전체 노트 수','Active Notes':'사용 중인 노트','Deleted Notes':'삭제된 노트','Protected Notes':'보호된 노트','📝 Notes Distribution':'📝 노트 분포','Breakdown of attachments by MIME type':'MIME 유형별 첨부 파일 분포','🖼️ Attachments by Type':'🖼️ 유형별 첨부 파일','Distribution of notes by their content type':'내용 유형별 노트 분포','📄 Notes by Content Type':'📄 내용 유형별 노트','📈 Trends & Time Series':'📈 시간에 따른 변화','Growth of notes over time':'시간에 따른 노트 수 변화','Total Notes':'전체 노트','📈 Notes Growth Over Time':'📈 시간에 따른 노트 수 변화','Attachment storage trends':'첨부 파일 저장량 변화','Active Attachments':'사용 중인 첨부 파일','Total Attachments':'전체 첨부 파일','📎 Attachments Growth Over Time':'📎 시간에 따른 첨부 파일 수 변화','🔧 Advanced Metrics':'🔧 고급 지표','Number of branches connecting notes':'노트를 연결하는 가지 수','🌳 Total Branches':'🌳 전체 가지','Number of note attributes':'노트 속성 수','🏷️ Attributes':'🏷️ 속성','Number of note revisions':'노트 수정 이력 수','🔄 Revisions':'🔄 수정 이력','Number of ETAPI tokens':'ETAPI 토큰 수','🔑 API Tokens':'🔑 API 토큰','Various storage and system metrics':'저장 공간과 시스템의 주요 지표','Blob Records':'본문 데이터 레코드','Recent Notes':'최근 노트','Embeddings':'임베딩','Embedding Providers':'임베딩 제공자','📊 Storage & System Metrics':'📊 저장 공간과 시스템 지표','Timeline showing when content was created and last modified':'콘텐츠 생성 시각과 마지막 수정 시각','Oldest Note':'가장 오래된 노트','Newest Note':'가장 최근 노트','Last Modified':'마지막 수정','⏰ Content Timeline':'⏰ 콘텐츠 생성 및 수정 시각','TriliumNext Dashboard':'TriliumNext 대시보드','Build Date':'빌드 날짜','Git Revision':'Git 커밋','Sync Version':'동기화 버전','DB Version':'데이터베이스 버전','Version':'버전','Datasource':'데이터 소스','Instance':'실행 환경','Job':'작업'};
|
||||
for(const[k,v]of Object.entries(dashboardMap))glossary.set(k.replace(/^\P{L}+/u,''),v.replace(/^\P{L}+/u,''));
|
||||
const ocr=JSON.parse(fs.readFileSync(b+'/help-image-ocr.json','utf8').replace(/^\uFEFF/,''));
|
||||
const byFile=new Map(ocr.filter(x=>x.latinWords>0).map(x=>[x.file,x]));
|
||||
const esc=s=>s.replaceAll('&','&').replaceAll('<','<').replaceAll('>','>').replaceAll('"','"');
|
||||
const used=new Set();
|
||||
const explanations={
|
||||
'표시점의 아이콘과 색상':'지도 위 표시점은 연결된 노트의 아이콘과 색상을 따릅니다. 표시점을 누르면 해당 노트를 열 수 있습니다.',
|
||||
'GPX 파일 가져오기':'GPX는 이동 경로를 저장하는 파일 형식입니다. 경로 파일을 지도 노트로 가져오는 과정을 보여 줍니다.',
|
||||
'지도에 표시된 경로':'가져온 경로가 지도 위의 선으로 표시됩니다. 지도 지명과 경로 이름은 예제 데이터입니다.',
|
||||
'문단 도구 모음':'문단 도구 모음에서 제목 단계와 목록 및 정렬 방식을 고릅니다. 아이콘 위에 마우스를 올리면 기능 이름을 확인할 수 있습니다.',
|
||||
'글자 도구 모음':'선택한 글자에 굵게와 기울임 및 밑줄 같은 서식을 적용하는 도구 모음입니다.',
|
||||
'테마 보관용 노트':'사용자 지정 테마 코드를 보관할 노트입니다. 노트 제목과 CSS 코드를 구분해서 입력하세요.',
|
||||
'탭에 표시된 작업 공간':'현재 작업 공간의 이름이 탭에 표시됩니다. 작업 공간을 바꾸면 해당 공간에 저장된 탭 구성을 불러옵니다.',
|
||||
'실행 도구 모음 설정':'실행 도구 모음에 표시할 항목을 설정하는 화면입니다. 각 실행 도구가 어떤 노트나 기능을 여는지 본문 설명과 함께 확인하세요.',
|
||||
'안내 상자 메뉴':'일반 인용문과 주의 사항 등의 안내 상자 모양을 고르는 메뉴입니다.',
|
||||
'JavaScript 구문 강조를 적용한 코드 블록':'코드 언어를 JavaScript로 지정한 예시입니다. 색으로 구분된 영문은 실행할 코드이므로 그대로 유지합니다.',
|
||||
'각주 예시':'본문의 작은 각주 번호와 아래쪽 각주 내용이 연결된 모습입니다. 예문 대신 원하는 설명을 입력하세요.',
|
||||
'글자 서식 팝업':'글자를 선택하면 나타나는 서식 메뉴입니다. 원하는 아이콘을 눌러 선택한 부분의 서식을 바꿉니다.',
|
||||
'색상 선택':'글자색이나 배경색을 고르는 화면입니다. 색상 견본에서 원하는 색을 선택합니다.',
|
||||
'이모지 선택기':'본문에 넣을 이모지를 검색하고 선택하는 창입니다. 현재 앱의 이모지 이름은 한국어로 표시됩니다.',
|
||||
'이모지 자동 완성':'이모지 이름을 입력했을 때 표시되는 후보 목록입니다. 원하는 후보를 선택하면 본문에 이모지가 들어갑니다.',
|
||||
'본문 안의 Mermaid 도표':'Mermaid 문법으로 작성한 도표를 본문 안에 표시한 예시입니다. 도표의 글자는 코드에 입력한 내용에 따라 달라집니다.',
|
||||
'가로 구분선':'문단 사이에 가로선을 넣어 내용을 구분한 예시입니다.',
|
||||
'페이지 나누기 표시':'인쇄할 때 새 페이지를 시작할 위치를 표시한 예시입니다.',
|
||||
'수식 입력 창':'수식 원문을 입력하고 결과를 확인하는 창입니다. 수학 기호와 LaTeX 명령은 번역하지 않고 입력합니다.',
|
||||
'들여쓰기 예시':'문단의 시작 위치를 안쪽으로 옮긴 모습입니다. 들여쓰기 늘리기와 줄이기로 깊이를 조절합니다.',
|
||||
'Ollama 정상 실행 응답':'Ollama 서버가 실행 중임을 알리는 응답입니다. 이 응답이 보이면 본문에 안내된 주소를 연결 설정에 사용합니다.',
|
||||
'여러 날짜를 선택해 일정 만들기':'달력에서 날짜 범위를 선택해 여러 날에 걸친 일정을 만드는 예시입니다.',
|
||||
'일정에 속성 표시':'달력의 일정에 노트 속성 값을 함께 표시한 모습입니다. 예제의 속성 이름과 값을 본문 설정과 비교하세요.',
|
||||
'여러 노트 선택':'노트 트리에서 여러 항목이 동시에 선택된 모습입니다. 선택한 노트들에 같은 작업을 적용할 수 있습니다.',
|
||||
'코드 블록 예시':'코드 블록에 입력한 프로그램 예시입니다. 영문 명령과 기호는 프로그램 문법이므로 원래 표기를 유지합니다.'
|
||||
};
|
||||
for(const rel0 of fs.readdirSync(src,{recursive:true}).filter(p=>p.endsWith('.html'))){
|
||||
const rel=rel0.replaceAll('\\','/'),old=fs.readFileSync(src+'/'+rel,'utf8');let idx=0;
|
||||
const annotations=[];
|
||||
const html=old.replace(/<img\b[^>]*>/gi,tag=>{
|
||||
idx++;const url=tag.match(/\bsrc="([^"]+)"/i)?.[1];if(!url)return tag;
|
||||
let img;try{img=path.posix.normalize(path.posix.join(path.posix.dirname(rel),decodeURIComponent(url.replaceAll('&','&'))));}catch{return tag;}
|
||||
const record=byFile.get(img);if(!record)return tag;
|
||||
used.add(img);
|
||||
const alt=tag.match(/\balt="([^"]*)"/i)?.[1]||'';
|
||||
const pairs=[...glossary].filter(([k])=>new RegExp('(?:^|[^a-z])'+k.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')+'(?:$|[^a-z])','i').test(record.text)).sort((a,c)=>c[0].length-a[0].length).filter((entry,i,all)=>!all.slice(0,i).some(([k])=>k.toLowerCase().includes(entry[0].toLowerCase()))).slice(0,12);
|
||||
annotations.push({image:img,number:idx,description:alt,pairs});
|
||||
return tag;
|
||||
});
|
||||
if(!annotations.length)continue;
|
||||
let appendix='<section class="korean-image-guide"><h2>예전 화면 이미지 읽는 법</h2><p>아래 설명은 본문에 나온 이미지 순서에 맞췄습니다. 이미지는 원본을 유지했으므로 영문 메뉴가 보일 수 있습니다. 현재 앱에서는 한국어 메뉴를 사용합니다. 코드와 주소 및 제품 이름은 원래 표기를 따릅니다.</p>';
|
||||
for(const a of annotations){appendix+=`<h3>이미지 ${a.number}${/[가-힣]/.test(a.description)?' · '+a.description:''}</h3>`;if(a.pairs.length)appendix+='<p>화면에서 사용하는 주요 표현은 다음과 같습니다.</p><ul>'+a.pairs.map(([e,k])=>`<li><span lang="en">${esc(e)}</span>: ${esc(k)}</li>`).join('')+'</ul>';else {if(!explanations[a.description])throw Error('Missing image explanation '+a.image);appendix+='<p>'+esc(explanations[a.description])+'</p>';}}
|
||||
appendix+='</section>';
|
||||
const updated=/<\/body>/i.test(html)?html.replace(/<\/body>/i,appendix+'</body>'):html+'\n'+appendix+'\n';
|
||||
const links=s=>[...s.matchAll(/\b(?:href|src)="[^"]*"/g)].map(x=>x[0]).sort();
|
||||
if(JSON.stringify(links(old))!==JSON.stringify(links(updated)))throw Error('Link changed '+rel);
|
||||
save('doc_notes/en/'+rel,old,updated);audit.push({file:rel,images:annotations});
|
||||
}
|
||||
const dashboard='User Guide/User Guide/Advanced Usage/Metrics/grafana-dashboard.json';
|
||||
const oldDashboard=fs.readFileSync(src+'/'+dashboard,'utf8');let replaced=0;
|
||||
const translated=JSON.parse(oldDashboard,(key,v)=>typeof v==='string'&&Object.hasOwn(dashboardMap,v)?(replaced++,dashboardMap[v]):v);
|
||||
save('doc_notes/en/'+dashboard,oldDashboard,JSON.stringify(translated,null,2)+'\n');
|
||||
const oldMain=fs.readFileSync(b+'/phase7/main.cjs','utf8');
|
||||
const next=oldMain.replace('jg=`assets/v${CR.version}-ko-20260913-r6`','jg=`assets/v${CR.version}-ko-20260913-r7`');if(oldMain===next)throw Error('Cache marker missing');save('main.cjs',oldMain,next);
|
||||
const report={htmlPages:audit.length,referencedImages:used.size,detectedImages:byFile.size,unreferenced:[...byFile.keys()].filter(k=>!used.has(k)),dashboardStrings:replaced,images:audit};
|
||||
fs.writeFileSync(out+'/image-guide-audit.json',JSON.stringify(report,null,2));fs.writeFileSync(out+'/manifest.json',JSON.stringify({files,previous},null,2));
|
||||
console.log({...report,images:undefined});
|
||||
@@ -0,0 +1,25 @@
|
||||
import pathlib,subprocess,datetime,shutil,time,urllib.request,hashlib,json
|
||||
base=pathlib.Path('/www/dk_project/dk_app/trilium');release=pathlib.Path('/tmp/trilium-korean-phase7')
|
||||
manifest=json.loads((release/'manifest.json').read_text());digest=lambda b:hashlib.sha256(b).hexdigest()
|
||||
targets={'main.cjs':'patched/main.cjs','layout_commons.js':'patched/layout_commons.js'}
|
||||
assert digest((base/targets['main.cjs']).read_bytes())==manifest['previousMainSha256']
|
||||
assert digest((base/targets['layout_commons.js']).read_bytes())==manifest['previousLayoutSha256']
|
||||
for rel,expected in manifest['files'].items():assert digest((release/rel).read_bytes())==expected
|
||||
backup=base/'backups'/('before-korean-phase7-'+datetime.datetime.now().strftime('%Y%m%d-%H%M%S'));backup.mkdir(parents=True,mode=0o700)
|
||||
for name,rel in targets.items():shutil.copy2(base/rel,backup/name)
|
||||
try:
|
||||
for name,rel in targets.items():(base/rel).write_bytes((release/name).read_bytes())
|
||||
subprocess.run(['docker','restart','trilium'],check=True)
|
||||
for attempt in range(40):
|
||||
try:
|
||||
with urllib.request.urlopen('http://127.0.0.1:18082/api/health-check',timeout=2) as response:assert response.status==200
|
||||
with urllib.request.urlopen('http://127.0.0.1:18082/assets/v0.95.0-ko-20260913-r6/src/layout_commons.js',timeout=3) as response:assert digest(response.read())==manifest['files']['layout_commons.js']
|
||||
break
|
||||
except Exception:
|
||||
if attempt==39:raise
|
||||
time.sleep(1)
|
||||
result={'status':'verified','backup':str(backup),'assetVersion':'v0.95.0-ko-20260913-r6','layoutSha256':manifest['files']['layout_commons.js']}
|
||||
(base/'i18n_ko/phase7-result.json').write_text(json.dumps(result,indent=2));print(json.dumps(result))
|
||||
except Exception:
|
||||
for name,rel in targets.items():(base/rel).write_bytes((backup/name).read_bytes())
|
||||
subprocess.run(['docker','restart','trilium'],check=True);raise
|
||||
@@ -0,0 +1,30 @@
|
||||
import pathlib,subprocess,datetime,shutil,time,urllib.request,urllib.parse,hashlib,json
|
||||
base=pathlib.Path('/www/dk_project/dk_app/trilium');release=pathlib.Path('/tmp/trilium-korean-phase8')
|
||||
manifest=json.loads((release/'manifest.json').read_text());digest=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
def target(rel):return base/('patched/main.cjs' if rel=='main.cjs' else 'i18n_ko/'+rel)
|
||||
for rel,expected in manifest['files'].items():
|
||||
assert digest(release/rel)==expected,rel
|
||||
assert digest(target(rel))==manifest['previous'][rel],rel
|
||||
backup=base/'backups'/('before-korean-phase8-'+datetime.datetime.now().strftime('%Y%m%d-%H%M%S'));backup.mkdir(parents=True,mode=0o700)
|
||||
for rel in manifest['files']:
|
||||
dest=backup/rel;dest.parent.mkdir(parents=True,exist_ok=True);shutil.copy2(target(rel),dest)
|
||||
try:
|
||||
for rel in manifest['files']:target(rel).write_bytes((release/rel).read_bytes())
|
||||
subprocess.run(['docker','restart','trilium'],check=True)
|
||||
for attempt in range(40):
|
||||
try:
|
||||
with urllib.request.urlopen('http://127.0.0.1:18082/api/health-check',timeout=2) as response:assert response.status==200
|
||||
break
|
||||
except Exception:
|
||||
if attempt==39:raise
|
||||
time.sleep(1)
|
||||
checked=0
|
||||
for rel,expected in manifest['files'].items():
|
||||
if rel=='main.cjs':continue
|
||||
with urllib.request.urlopen('http://127.0.0.1:18082/assets/v0.95.0-ko-20260913-r7/'+urllib.parse.quote(rel),timeout=5) as response:assert hashlib.sha256(response.read()).hexdigest()==expected,rel
|
||||
checked+=1
|
||||
result={'status':'verified','backup':str(backup),'servedFiles':checked,'assetVersion':'v0.95.0-ko-20260913-r7'}
|
||||
(base/'i18n_ko/phase8-result.json').write_text(json.dumps(result,indent=2));print(json.dumps(result))
|
||||
except Exception:
|
||||
for rel in manifest['files']:target(rel).write_bytes((backup/rel).read_bytes())
|
||||
subprocess.run(['docker','restart','trilium'],check=True);raise
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
Add-Type -AssemblyName System.Runtime.WindowsRuntime
|
||||
$null = [Windows.Storage.StorageFile, Windows.Storage, ContentType=WindowsRuntime]
|
||||
$null = [Windows.Storage.Streams.IRandomAccessStream, Windows.Storage.Streams, ContentType=WindowsRuntime]
|
||||
$null = [Windows.Graphics.Imaging.BitmapDecoder, Windows.Graphics.Imaging, ContentType=WindowsRuntime]
|
||||
$null = [Windows.Graphics.Imaging.SoftwareBitmap, Windows.Graphics.Imaging, ContentType=WindowsRuntime]
|
||||
$null = [Windows.Media.Ocr.OcrEngine, Windows.Foundation, ContentType=WindowsRuntime]
|
||||
$null = [Windows.Media.Ocr.OcrResult, Windows.Foundation, ContentType=WindowsRuntime]
|
||||
$asyncMethod = [System.WindowsRuntimeSystemExtensions].GetMethods() | Where-Object { $_.Name -eq 'AsTask' -and $_.IsGenericMethod -and $_.GetParameters().Count -eq 1 -and $_.GetParameters()[0].ParameterType.Name -eq 'IAsyncOperation`1' } | Select-Object -First 1
|
||||
function AwaitOperation($operation, $resultType) {
|
||||
$task = $asyncMethod.MakeGenericMethod($resultType).Invoke($null, @($operation))
|
||||
$task.Wait()
|
||||
return $task.Result
|
||||
}
|
||||
$engine = [Windows.Media.Ocr.OcrEngine]::TryCreateFromUserProfileLanguages()
|
||||
if ($null -eq $engine) { throw 'OCR language unavailable' }
|
||||
$sourceRoot = (Resolve-Path 'work/trilium/phase3/doc_notes/en').Path
|
||||
$items = Get-ChildItem -LiteralPath $sourceRoot -Recurse -File | Where-Object {$_.Extension -in '.png','.jpg','.gif'}
|
||||
$results = [System.Collections.Generic.List[object]]::new()
|
||||
foreach ($entry in $items) {
|
||||
try {
|
||||
$file = AwaitOperation ([Windows.Storage.StorageFile]::GetFileFromPathAsync($entry.FullName)) ([Windows.Storage.StorageFile])
|
||||
$stream = AwaitOperation ($file.OpenAsync([Windows.Storage.FileAccessMode]::Read)) ([Windows.Storage.Streams.IRandomAccessStream])
|
||||
$decoder = AwaitOperation ([Windows.Graphics.Imaging.BitmapDecoder]::CreateAsync($stream)) ([Windows.Graphics.Imaging.BitmapDecoder])
|
||||
$bitmap = AwaitOperation ($decoder.GetSoftwareBitmapAsync()) ([Windows.Graphics.Imaging.SoftwareBitmap])
|
||||
$ocr = AwaitOperation ($engine.RecognizeAsync($bitmap)) ([Windows.Media.Ocr.OcrResult])
|
||||
$latin = [regex]::Matches($ocr.Text,'[A-Za-z]{3,}').Count
|
||||
$results.Add([pscustomobject]@{file=$entry.FullName.Substring($sourceRoot.Length+1).Replace('\','/');latinWords=$latin;text=$ocr.Text;error=$null})
|
||||
$bitmap.Dispose();$stream.Dispose()
|
||||
} catch {
|
||||
$results.Add([pscustomobject]@{file=$entry.FullName.Substring($sourceRoot.Length+1).Replace('\','/');latinWords=-1;text='';error=$_.Exception.Message})
|
||||
}
|
||||
if ($results.Count % 30 -eq 0) { Write-Output ('OCR processed: '+$results.Count+'/'+$items.Count) }
|
||||
}
|
||||
$utf8 = New-Object System.Text.UTF8Encoding($false)
|
||||
[System.IO.File]::WriteAllText((Join-Path (Get-Location) 'work/trilium/help-image-ocr.json'),($results | ConvertTo-Json -Depth 5),$utf8)
|
||||
Write-Output ('OCR complete: '+$results.Count)
|
||||
Reference in New Issue
Block a user