
Korean Spell Check
Batch-check Korean copy in docs, landing pages, or support macros via the Nara speller service with chunked requests and structured issue reports.
Overview
Korean spell-check is an agent skill most often used in Grow (also Ship launch prep and Build docs) that finds Korean spelling and grammar issues via chunked requests to Nara speller and returns structured corrections.
Install
npx skills add https://github.com/nomadamas/k-skill --skill korean-spell-checkWhat is this skill?
- Posts text to Nara speller with configurable timeout (default 30s) and polite throttle (default 1.2s between chunks)
- Splits long input at ~1500 characters with sentence and paragraph-aware chunking
- Parses HTML results into structured issues: original, suggestions, reason, and offsets
- Detects clean runs when the service reports no grammar or spelling errors
- CLI-oriented Python workflow with JSON-friendly dataclass output for agents
- Default 1500 max characters per chunk
- Default 1.2s throttle between requests
- Default 30s HTTP timeout
Adoption & trust: 2.4k installs on skills.sh; 5.4k GitHub stars; 2/3 security scanners passed (skills.sh audits).
What problem does it solve?
You have Korean marketing or product copy and need a repeatable spell pass without manually pasting paragraphs into a web form one screen at a time.
Who is it for?
Indie builders localizing to Korean, Korean-native founders polishing launch copy, or agents batch-reviewing README and landing MD before ship.
Skip if: English-only products with no Korean strings, legal copy requiring certified human proofreading only, or environments that block outbound HTTP to third-party spell services.
When should I use this skill?
User needs Korean spelling and grammar review on drafts, UI strings, or docs using the Nara speller flow with structured issue output.
What do I get? / Deliverables
After the skill runs, you get a structured list of suspected errors with suggestions and reasons you can apply in CMS files, MDX, or translation bundles.
- Structured spell-check issue list with suggestions
- Chunk-indexed report suitable for JSON export
Recommended Skills
Journey fit
Spans multiple journey phases - primary shelf plus alternate fits below.
Polished Korean copy most often ships with growth and content work, so the canonical shelf is Grow—even though you may run checks right before launch or while writing docs. Content is where spell-check outcomes directly affect emails, blog posts, in-app strings, and localization handoffs.
Where it fits
Run chunked spell-check on a Korean blog draft before scheduling publication.
Scan launch announcement Korean text for grammar issues minutes before going live.
Validate Korean README sections after translating setup instructions.
Proofread Korean hero and pricing blurbs on a validation landing page.
How it compares
Use instead of ad-hoc copy-paste into browser spellers when you need chunked, agent-parseable Korean grammar results.
Common Questions / FAQ
Who is korean-spell-check for?
Solo builders and small teams publishing Korean UI strings, docs, or growth content who want automated checks integrated into an agent workflow.
When should I use korean-spell-check?
Use it while preparing Grow content, tightening Ship launch copy, or finalizing Build-phase Korean documentation—any time Korean text is ready for a consistency pass before publish.
Is korean-spell-check safe to install?
Check the Security Audits panel on this Prism page; the skill calls an external Korean speller website and sends your text over the network, so avoid secrets or PII in submitted strings.
SKILL.md
READMESKILL.md - Korean Spell Check
from __future__ import annotations import argparse import json import re import sys import time import urllib.error import urllib.parse import urllib.request from dataclasses import asdict, dataclass from html import unescape from pathlib import Path from typing import Callable DEFAULT_RESULTS_URL = "https://nara-speller.co.kr/old_speller/results" DEFAULT_MAX_CHARS = 1500 DEFAULT_TIMEOUT = 30 DEFAULT_THROTTLE_SECONDS = 1.2 RESULT_PAYLOAD_PATTERN = re.compile(r"data\s*=\s*(\[[\s\S]*?\]);\s*pageIdx\s*=") NO_ISSUES_PATTERN = re.compile(r"맞춤법과\s*문법\s*오류를\s*찾지\s*못했습니다", re.MULTILINE) TAG_PATTERN = re.compile(r"<[^>]+>") LINE_BREAK_PATTERN = re.compile(r"<br\s*/?>", re.IGNORECASE) SENTENCE_BOUNDARY_PATTERN = re.compile(r"(?<=[.!?。!?])\s+") PARAGRAPH_SEPARATOR_PATTERN = re.compile(r"\n(?:[ \t]*\n)+") DEFAULT_HEADERS = { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "ko,en-US;q=0.9,en;q=0.8", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Origin": "https://nara-speller.co.kr", "Referer": "https://nara-speller.co.kr/old_speller/", "User-Agent": ( "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36" ), } @dataclass(frozen=True) class SpellCheckIssue: chunk_index: int page_index: int issue_index: int sentence: str original: str suggestions: list[str] reason: str start: int | None end: int | None correct_method: int | None error_message: str def strip_html(value: str | None) -> str: text = LINE_BREAK_PATTERN.sub("\n", value or "") text = TAG_PATTERN.sub("", text) return unescape(text).strip() def split_candidates(value: str | None) -> list[str]: return [candidate.strip() for candidate in str(value or "").split("|") if candidate.strip()] def parse_positive_int(raw_value: str) -> int: value = int(raw_value) if value <= 0: raise argparse.ArgumentTypeError("must be a positive integer") return value def split_text_into_chunks(text: str, max_chars: int = DEFAULT_MAX_CHARS) -> list[str]: original = str(text or "") if not original.strip(): return [] units = split_paragraph_units(original) chunks: list[str] = [] current = "" for unit in units: candidate = unit if not current else f"{current}{unit}" if len(candidate) <= max_chars: current = candidate continue if current: chunks.append(current) current = "" if len(unit) <= max_chars: current = unit continue separator = "" body = unit separator_match = PARAGRAPH_SEPARATOR_PATTERN.search(unit) if separator_match and separator_match.end() == len(unit): separator = separator_match.group(0) body = unit[: separator_match.start()] for sentence in split_long_paragraph(body, max_chars=max_chars): if len(sentence) <= max_chars: chunks.append(sentence) continue start = 0 while start < len(sentence): chunks.append(sentence[start : start + max_chars]) start += max_chars if separator: if chunks and len(chunks[-1]) + len(separator) <= max_chars: chunks[-1] += separator else: current = separator if current: chunks.append(current) return chunks def split_paragraph_units(text: str) -> list[str]: units: list[str] = [] start = 0 for match in PARAGRAPH_SEPARATOR_PATTERN.finditer(text): paragraph = text[start : match.start()] separator = match.group(0) if paragraph: units.append(paragraph + separator) elif units: units[-1] += separator else: units.append(separator)