diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 283966d..9f625b1 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -41,11 +41,11 @@ jobs:
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
- public/fonts/*.woff2
- public/fonts/fonts-*.css
+ app/assets/fonts/*.woff2
+ app/assets/fonts/fonts-*.css
# chars.json exists here because Build dataset runs first, even
# though the generated file is ignored by Git.
- key: fonts-v1-${{ runner.os }}-${{ hashFiles('data/sources.lock.json', 'package.json', 'pnpm-lock.yaml', 'scripts/build-fonts.ts', 'scripts/sources.ts', 'shared/links.ts', 'shared/row.ts', 'shared/types.ts', 'app/locales/*.ts', 'public/data/chars.json') }}
+ key: fonts-v2-${{ runner.os }}-${{ hashFiles('data/sources.lock.json', 'package.json', 'pnpm-lock.yaml', 'scripts/build-fonts.ts', 'scripts/sources.ts', 'shared/links.ts', 'shared/row.ts', 'shared/types.ts', 'app/locales/*.ts', 'app/assets/data/chars.json') }}
- name: Build fonts
if: steps.fonts-cache.outputs.cache-hit != 'true'
diff --git a/.gitignore b/.gitignore
index 6b2e376..d0bf8a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,19 +12,20 @@ dist
# Cached downloads of external sources, verified against data/sources.lock.json
data/raw/
-# Generated by pnpm build:dataset. NOTICE.md remains committed so source terms
-# stay visible without running the pipeline.
-public/data/chars.json
-public/data/sources.json
+# Generated by pnpm build:dataset and consumed through Vite's asset graph.
+# The stable data-source notice remains committed under public/notices/.
+app/assets/data/chars.json
+app/assets/strokes/
# Generated by pnpm build:data: 12MB of font binaries and the @font-face rules
-# that point at them.
-public/fonts/*.woff2
-public/fonts/fonts-*.css
+# that point at them. Vite fingerprints both the CSS and font files.
+app/assets/fonts/*.woff2
+app/assets/fonts/fonts-*.css
-# Generated by pnpm build:dataset: regional stroke shards and their licenses.
-# They are copied into the deploy output but are not committed.
-public/strokes/
+# Generated verbatim from the pinned AnimCJK source. These keep stable public
+# URLs and are revalidated instead of entering Vite's fingerprinted assets.
+public/notices/animcjk-APL.txt
+public/notices/animcjk-COPYING.txt
# Local record of the development conversation: 11MB of JSONL plus screenshots.
# Kept on disk for reference, out of the repository by default.
diff --git a/.prettierignore b/.prettierignore
index f4c12dc..efdc50f 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,8 +1,7 @@
# Generated by scripts/build-data.ts and not committed.
-public/data/chars.json
-public/data/sources.json
-public/strokes/
+app/assets/data/chars.json
+app/assets/strokes/
# Generated by scripts/build-fonts.ts alongside the woff2 files
-public/fonts/fonts-*.css
+app/assets/fonts/fonts-*.css
app/generated/
diff --git a/AGENTS.md b/AGENTS.md
index 0ca69d3..5968ce8 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2,7 +2,7 @@
## Project Structure & Module Organization
-Hanji is a Nuxt 4/Vue 3 static site. Route components live in `app/pages/`, reusable UI in `app/components/`, stateful helpers in `app/composables/`, and global styling in `app/styles/`. Keep framework-independent data models and lookup logic in `shared/` so both the app and build scripts can import them. Data and font pipelines live in `scripts/`; their Vitest suites are in `scripts/tests/`. `public/data/` contains committed generated datasets, while generated font files and downloaded inputs under `data/raw/` are ignored. Do not hand-edit generated outputs; change the relevant script or source lock instead.
+Hanji is a Nuxt 4/Vue 3 static app. Route components live in `app/pages/`, reusable UI in `app/components/`, stateful helpers in `app/composables/`, and global styling in `app/styles/`. Keep framework-independent data models and lookup logic in `shared/` so both the app and build scripts can import them. Data and font pipelines live in `scripts/`; their Vitest suites are in `scripts/tests/`. Generated character data, stroke shards, and font subsets under `app/assets/` are ignored and fingerprinted by Vite; stable legal texts live under `public/notices/`. Downloaded inputs under `data/raw/` are also ignored. Do not hand-edit generated outputs; change the relevant script or source lock instead.
## Build, Test, and Development Commands
@@ -13,7 +13,7 @@ Hanji is a Nuxt 4/Vue 3 static site. Route components live in `app/pages/`, reus
- `pnpm lint` checks TypeScript, Vue, and UnoCSS conventions.
- `pnpm typecheck` runs Nuxt's Vue-aware TypeScript checker.
- `pnpm format` formats the repository with the shared Prettier configuration.
-- `pnpm generate` writes the deployable static site to `.output/public/`.
+- `pnpm generate` writes the deployable static app to `.output/public/`.
## Coding Style & Naming Conventions
diff --git a/PRODUCT.md b/PRODUCT.md
index fe72157..2af99ab 100644
--- a/PRODUCT.md
+++ b/PRODUCT.md
@@ -40,7 +40,7 @@ and choose the regions participating in a comparison. Character detail pages
provide stable direct URLs, regional metadata, readings, stroke-order material
where available, related forms, and links to external dictionaries. The
interface supports Simplified Chinese, Hong Kong Traditional Chinese, Taiwan
-Traditional Chinese, Japanese, and Korean. The product is a static website
+Traditional Chinese, Japanese, and Korean. The product is a static web app
with no account or server-side workflow, and its generated datasets are also
published as downloadable files.
@@ -82,15 +82,18 @@ language so that every comparison displays the intended local form.
- `README.md` and the localized About page document scope, methodology,
limitations, naming, and deployment.
-- `public/data/chars.json`, `public/data/sources.json`, and
- `public/data/NOTICE.md` expose the generated character data, source metadata,
- licenses, and transformation notes.
+- `app/assets/data/chars.json` provides the generated character data through a
+ Vite-fingerprinted asset. Static generation also copies it to the stable
+ `/data/chars.json` URL for external consumers. Attribution metadata lives in
+ `shared/sources.ts`, while `public/notices/data-sources.md` keeps
+ transformation and license notes at a stable, revalidated URL.
- `data/sources.lock.json` pins third-party inputs and checksums.
- The data and font pipelines under `scripts/`, together with their Vitest
suites, encode and verify grouping, source, mapping, and rendered-outline
guarantees.
- Region-specific Noto CJK font subsets and local interface fonts are generated
- for the site, with the applicable font license included in `public/fonts/`.
+ into `app/assets/fonts/`, with the applicable font license included in
+ `public/notices/`.
- The repository contains no testimonials, institutional endorsements,
commercial benchmarks, or other proof that future work may fabricate.
diff --git a/README.ja-JP.md b/README.ja-JP.md
index 1085beb..dee2e7b 100644
--- a/README.ja-JP.md
+++ b/README.ja-JP.md
@@ -4,11 +4,11 @@
-中国大陸・香港・台湾・日本・韓国の5地域で一般的に使われる漢字をまとめた字表です。本サイトでは、一般的なゴシック体と明朝体における印刷字形を比較します。同じ字でも、地域によって異なる字形で表示されることがあります。5地域の字形を横に並べ、既定ではUIの言語に対応する地域の文字頻度順で表示します。画数順やコードポイント順への並べ替え、差異パターンによる絞り込みも可能です。韓国には文字頻度データがないため、韓国列は既定で非表示ですが、表示オプションから有効にできます。
+中国大陸・香港・台湾・日本・韓国の5地域で一般的に使われる漢字をまとめた字表です。本アプリでは、一般的なゴシック体と明朝体における印刷字形を比較します。同じ字でも、地域によって異なる字形で表示されることがあります。5地域の字形を横に並べ、既定ではUIの言語に対応する地域の文字頻度順で表示します。画数順やコードポイント順への並べ替え、差異パターンによる絞り込みも可能です。韓国には文字頻度データがないため、韓国列は既定で非表示ですが、表示オプションから有効にできます。
字形は一つの側面にすぎません。収録範囲は5地域の常用字表の和集合であり、**字形が完全に同じ字も収録しています**。これは5地域の漢字に関する資料表であり、差異だけを並べた一覧ではありません。
-以下は概要です。項目ごとの詳しい説明は、サイト内の「このサイトについて」ページにあります。
+以下は概要です。項目ごとの詳しい説明は、アプリ内の「アプリについて」ページにあります。
## 名前の由来
@@ -36,13 +36,13 @@
判定にはAdobe Source Han SansとSource Han Serifを使い、ページ上の表示には同系統のNoto Sans CJKとNoto Serif CJKを使います。各フォントでは、中国大陸・香港・台湾・日本・韓国の5地域が一つのグリフプールを共有し、地域ごとに「Unicodeコードポイント → グリフ番号(CID)」の対応を持ちます。二つの地域が同じCIDに対応していれば、同じ字形と見なします。Adobeはこれらの対応をプレーンテキストで公開しているため、アウトラインやレンダリング画像を比較する必要はありません。
-最終判定には、**ゴシック体と明朝体の結果の和集合**を使います。どちらか一方の書体で二つの地域が同じ字形なら、本サイトでも同形として扱います。これにより、一方のフォントだけに現れるデザイン上の細部を除外できます。たとえばSource Han Sansは、日本の常用漢字のおよそ5分の1に独立した字形を用意していますが、そのうち約200字はSource Han Serifでは区別されていません(了、人、子、水、金など)。本サイトでは、このような差を地域規範上の差異として数えません。判断の詳細は[データ規則と既知の制限](docs/known-issues.md)を参照してください。
+最終判定には、**ゴシック体と明朝体の結果の和集合**を使います。どちらか一方の書体で二つの地域が同じ字形なら、本アプリでも同形として扱います。これにより、一方のフォントだけに現れるデザイン上の細部を除外できます。たとえばSource Han Sansは、日本の常用漢字のおよそ5分の1に独立した字形を用意していますが、そのうち約200字はSource Han Serifでは区別されていません(了、人、子、水、金など)。本アプリでは、このような差を地域規範上の差異として数えません。判断の詳細は[データ規則と既知の制限](docs/known-issues.md)を参照してください。
ページ上ではこの判定に基づいてセルを再グループ化します。同形と判定されたセルは、グループ内の一地域のNotoフォントを共通して使い、画面上でも実際に同じ輪郭を表示します。そのため、上記ルールで除外された地域版間の細かな差異は表示されません。`scripts/tests/fonts.test.ts` はfontkitで生成フォントの実際のアウトラインを取り出し、判定と画面表示が一致することを1字ずつ検証します。
## 制限事項
-- 本サイトが比較するのは、一般的なゴシック体と明朝体における印刷字形だけです。手書きの慣習は対象外で、教科書体の例示字形も基準にしません。日本語の教科書体は主に日本語教育向けに設計され、中国大陸・香港・台湾・韓国と同じグリフプールを共有する正式な地域版がありません。見た目の近い別々のフォントを組み合わせると、地域差とフォント固有のデザイン差を分離できません。条件を揃えるため、5地域版を同時に提供する同系統のフォントファミリーだけを使います。
+- 本アプリが比較するのは、一般的なゴシック体と明朝体における印刷字形だけです。手書きの慣習は対象外で、教科書体の例示字形も基準にしません。日本語の教科書体は主に日本語教育向けに設計され、中国大陸・香港・台湾・韓国と同じグリフプールを共有する正式な地域版がありません。見た目の近い別々のフォントを組み合わせると、地域差とフォント固有のデザイン差を分離できません。条件を揃えるため、5地域版を同時に提供する同系統のフォントファミリーだけを使います。
- 判定対象はSource Hanの地域別字形デザインであり、各地域の標準そのものではありません。ただし高品質な代理です。Adobeの地域別字形は、中国大陸の『印刷通用漢字字形表』、台湾教育部の『國字標準字體』、香港教育局の『常用字字形表』、日本のJIS X 0208/0213、韓国のKS X 1001/1002をそれぞれ根拠としています。
- Source Hanの香港字形は網羅的ではないため、「香港だけが異なる」パターンは少なく報告される可能性があります。
- 画数は地域ごとに取得します。まずUnihanの `kAlternateTotalStrokes` を参照し、日本については次に `kRSAdobe_Japan1_6` を使います。これはAdobe-Japan1-6に収録された日本字形を分析するため、たとえば「突」は9画ではなく8画になります。どちらもない場合のみ `kTotalStrokes` を使います。最後の値は簡体・繁体の2区分しかなく、香港・台湾・日本・韓国は通常、繁体字側の値を共有します。たとえば「那」の5列の画数は6/6/6/7/6です。
@@ -53,9 +53,9 @@
## 免責事項
-本サイトは公開資料に基づいて作成した字形比較ツールであり、各地域の標準、辞書、教材ではありません。ページに表示される内容は、本サイトが採用したデータと自動処理規則による結果です。ある字について、その地域で唯一の「正しい」形であると断定する根拠にはできません。
+本アプリは公開資料に基づいて作成した字形比較ツールであり、各地域の標準、辞書、教材ではありません。ページに表示される内容は、本アプリが採用したデータと自動処理規則による結果です。ある字について、その地域で唯一の「正しい」形であると断定する根拠にはできません。
-各地域の標準は適用範囲や定義が完全には一致せず、フォントも標準の一つの設計実装にすぎません。本サイトでは異なる出典のデータを整理、変換、統合し、未収録項目には参考字形も補います。これらはすべてエンジニアリング上の判断であり、単純化、欠落、誤りが生じる可能性があります。
+各地域の標準は適用範囲や定義が完全には一致せず、フォントも標準の一つの設計実装にすぎません。本アプリでは異なる出典のデータを整理、変換、統合し、未収録項目には参考字形も補います。これらはすべてエンジニアリング上の判断であり、単純化、欠落、誤りが生じる可能性があります。
ページ上の「同形」または「異なる」という判定は、上記の資料、フォント、規則の範囲内でのみ成立します。地域の並びやグループ分けは比較を容易にするためのもので、優劣や立場を示すものではありません。正式な用途では原典の標準や辞書を確認してください。各字の詳細ページには、対応する地域の辞書へのリンクがあります。漢字は非常に多いため、誤りを完全には避けられません。データの誤りを見つけた場合は、[issue](https://github.com/sxzz/hanji/issues)でお知らせください。
@@ -67,16 +67,16 @@ pnpm build:data # 字表とフォントサブセットを生成。初回は約
pnpm update:sources # サードパーティデータの更新を確認して固定。変更があればダウンロードして再生成
pnpm dev
pnpm test
-pnpm generate # 静的サイト
+pnpm generate # 静的Webアプリ
```
各行のURLには行名を使います(`/char/着`)。5地域の表示字形、`aka`、`alternatives` もURLとして利用でき、クライアント側で対応する行へ移動します。たとえば `/char/国`、`/char/郞`、`/char/缐` です。ページの `rel=canonical` は行名のURLを指します。未確認関係はURL別名にはなりません。
-字表 `public/data/chars.json` と出典一覧 `public/data/sources.json` は、どちらも `pnpm build:dataset` で生成し、リポジトリには**コミットしません**。ビルド後も、それぞれ `/data/chars.json` と `/data/sources.json` から公開データとして取得できます。すべての `/data/*.json` レスポンスは任意のオリジンからのクロスオリジン読み取りを許可し、安定したURLで古いデータが残らないよう、キャッシュの再検証を必須にします。フォントサブセットは約12MBで、同様にコミットせず、`pnpm build:data` で生成します。そのため、ビルド前に一度実行する必要があります。元データのダウンロードは `data/raw/` 以下に種類別(`charlist/`、`opencc/`、`cmap/`、`font/`、`unihan/`、`frequency/`、`strokes/`)でキャッシュされ、gitignoreされています。ビルド時には、古いキャッシュから復元されたものの、現在の出典一覧には存在しないファイルを削除します。
+字表 `app/assets/data/chars.json` は `pnpm build:dataset` で生成し、リポジトリには**コミットしません**。アプリが直接インポートし、Viteも内容ハッシュ付きのダウンロードURLを出力します。静的生成の完了後、字表は外部サイトから参照できる固定URL `/data/chars.json` にもコピーされ、「アプリについて」ページもこのURLへリンクします。キャッシュは1時間有効で、期限切れ後もバックグラウンドで再検証している間は古い版を1日利用できます。出典とライセンスのメタデータは `shared/sources.ts` で直接管理し、ページとビルドスクリプトが共通でインポートするため、`sources.json` は生成しません。約12MBのフォントサブセットは `app/assets/fonts/` に生成し、同様にコミットしません。字表、筆順、フォント、旗はすべてViteのアセットグラフに入り、`/_nuxt/*` の長期immutableキャッシュで安全に再利用できます。変更され得る一方で安定URLが必要なNOTICEとライセンス文は `public/notices/` に分離し、利用のたびに再検証します。ビルド前には `pnpm build:data` を実行してください。元データのダウンロードは `data/raw/` 以下に種類別(`charlist/`、`opencc/`、`cmap/`、`font/`、`unihan/`、`frequency/`、`strokes/`)でキャッシュされ、gitignoreされています。ビルド時には、古いキャッシュから復元されたものの、現在の出典一覧には存在しないファイルを削除します。
## デプロイ
-静的サイトなので、`.output/public` を任意の静的ホスティングに配置できます。本番環境では、[GitHub Actions](.github/workflows/deploy.yml) がビルド後の成果物をCloudflare Workers Static Assetsへ直接アップロードします。
+静的Webアプリなので、`.output/public` を任意の静的ホスティングに配置できます。本番環境では、[GitHub Actions](.github/workflows/deploy.yml) がビルド後の成果物をCloudflare Workers Static Assetsへ直接アップロードします。
- `main` へのpushはproductionへデプロイします。
- `main` 向けのPRは `wrangler versions upload` で `pr-<番号>` preview aliasへデプロイします。GitHubのPRには対応するdeploymentとURLが表示され、その後のコミットでも同じプレビューURLを使います。
@@ -92,11 +92,11 @@ Cloudflareの **Settings → Domains & Routes** でproductionドメインを接
ページビューとWeb Vitalsが必要な場合は、実際のドメインを所有するアカウントの **Web Analytics → Add a site** でCloudflareによりプロキシされているhostnameを選び、automatic setupを使用してください。Cloudflareがエッジでbeaconを自動挿入します。
-各字群の詳細ページはそれぞれ独立したHTMLとして生成されます。ページデータはローカルbundleに含まれるため、ルートごとの追加 `_payload.json` を生成するpayload extractionは無効にしています。地域異体字の別名については、リダイレクト専用ページを生成しません。Static Assetsがまず `404.html` とHTTP 404を返し、その後Nuxtのクライアントミドルウェアが対応する行へ移動します。これにより、検索エンジンが別名を成功ページとして重複登録することを避けます。実際に存在しないURLはHTTP 404のままです。`public/_headers` では、内容ハッシュ付きの `_nuxt/*` に長期キャッシュを設定します。
+各字群の詳細ページはそれぞれ独立したHTMLとして生成されます。ページデータはローカルbundleに含まれるため、ルートごとの追加 `_payload.json` を生成するpayload extractionは無効にしています。地域異体字の別名については、リダイレクト専用ページを生成しません。Static Assetsがまず `404.html` とHTTP 404を返し、その後Nuxtのクライアントミドルウェアが対応する行へ移動します。これにより、検索エンジンが別名を成功ページとして重複登録することを避けます。実際に存在しないURLはHTTP 404のままです。`public/_headers` では、内容ハッシュ付きの `_nuxt/*` に長期immutableキャッシュを設定し、安定した `/notices/*` URLには `no-cache`、`/data/chars.json` には1時間の `max-age` と1日の `stale-while-revalidate` を指定します。
サードパーティ資産の具体的なcommit、公式添付ファイル識別子、SHA-256は `data/sources.lock.json` に記録しています。更新時には `pnpm update:sources` を実行します。バージョンのある上流データについてはバージョン番号を解決し、バージョンのない公式直リンクについては改めて検証します。内容が変わっていればlockfileを更新してデータを再生成し、まったく変わっていなければ生成をスキップします。ビルド時の `pnpm build:data` はlockfileに従い、約 **261 MiB** の元データをダウンロードして検証します。このうち195 MiBは10個のNoto CJKフォントです。明示的な更新を行っていない直リンクの内容が変わった場合は、チェックサム不一致として失敗し、黙ってデータに取り込むことはありません。Actionsでは元データのダウンロードと生成フォントを別々にキャッシュします。前者はlockfileだけで決まり、後者はlockfile、実際の生成スクリプト、関連依存関係、locale、字表から決まります。フォント入力が完全に同じ場合は、データ生成を省略します。
-筆順シャードと付属ライセンスは `pnpm build:dataset` が `public/strokes/` に生成し、リポジトリにはコミットしません。デプロイ処理はテストと静的生成の前に毎回これらを再生成します。同じ字グループ内で筆画順に並べた輪郭が完全に一致する場合は、最初のバリアントとその中心線だけを保存し、画面上でも対応する地域を1つの選択肢にまとめます。筆順シャードは安定したパスを使用し、専用のキャッシュポリシーは設定しません。
+筆順シャードは `pnpm build:dataset` が `app/assets/strokes/` に生成し、リポジトリにはコミットしません。デプロイ処理はテストと静的生成の前に毎回再生成し、Viteが内容ハッシュ付きのファイル名で出力します。付属ライセンスは `public/notices/` の安定URLに置き、再検証を必須にします。同じ字グループ内で筆画順に並べた輪郭が完全に一致する場合は、最初のバリアントとその中心線だけを保存し、画面上でも対応する地域を1つの選択肢にまとめます。ページ読み込み時に所属シャードを一度だけ取得し、その後の地域切替ではメモリ上の字グループデータを再利用します。
ローカルでも、ビルド後に直接アップロードできます。
@@ -130,7 +130,7 @@ pnpm deploy
1字ずつ比較できるツール [tofu.tools](https://tofu.tools/) は本プロジェクトの先行例で、同じくNotoファミリーを使って地域字形を区別しています。
-フォントはNoto Sans CJKとNoto Serif CJK(SIL OFL 1.1)を本サイトで使う文字にサブセット化したものです。ライセンス文は `/fonts/OFL.txt` に同梱しています。生成データファイルは上記の出典から派生しているため、それぞれのライセンスに従ってください。項目ごとの変換方法と帰属表示は、公開されている [`/data/NOTICE.md`](public/data/NOTICE.md) にも記載しています。
+フォントはNoto Sans CJKとNoto Serif CJK(SIL OFL 1.1)を本アプリで使う文字にサブセット化したものです。ライセンス文は [`/notices/noto-ofl.txt`](public/notices/noto-ofl.txt) に同梱しています。生成データファイルは上記の出典から派生しているため、それぞれのライセンスに従ってください。項目ごとの変換方法と帰属表示は、公開されている [`/notices/data-sources.md`](public/notices/data-sources.md) にも記載しています。
## License
diff --git a/README.md b/README.md
index a6511a3..2952f7a 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +4,11 @@
-中国大陆、香港、台湾、日本、韩国五地常用汉字的字表。本站对照的是通用黑体与宋体中的印刷字形:同一个字,在各地可能呈现不同字形。这里把五地字形并排列出,默认按界面语言对应地区的字频排序,也可按笔画或码点排序,并按差异模式筛选。韩国没有字频数据,韩国列默认关闭,可在显示选项中启用。
+中国大陆、香港、台湾、日本、韩国五地常用汉字的字表。本应用对照的是通用黑体与宋体中的印刷字形:同一个字,在各地可能呈现不同字形。这里把五地字形并排列出,默认按界面语言对应地区的字频排序,也可按笔画或码点排序,并按差异模式筛选。韩国没有字频数据,韩国列默认关闭,可在显示选项中启用。
字形只是其中一个维度。收录范围是五地常用字表的并集,**字形完全相同的字同样收录**:这是一份五地汉字的资料表,不是一份差异清单。
-下面是概要,逐条的完整说明在站内的「关于」页。
+下面是概要,逐条的完整说明在应用内的「关于」页。
## 名字的由来
@@ -36,13 +36,13 @@
判定使用 Adobe Source Han Sans 与 Source Han Serif,页面则用与它们同源的 Noto Sans CJK 与 Noto Serif CJK 显示结果。每套字体让中、港、台、日、韩五个地区共用一个字形池,并分别提供「Unicode 码点 → 字形编号(CID)」映射。两地映射到同一 CID,就视为同形。Adobe 已把这些映射以纯文本公开,因此不需要比对轮廓或渲染截图。
-最终判定取**黑体与宋体结果的并集**:只要其中一款把两地画成同一字形,本站就按同形处理。这能排除只出现在单款字体中的设计细节。例如 Source Han Sans 为约五分之一的日本常用汉字提供独立字形,其中约两百个在 Source Han Serif 并未区分(了、人、子、水、金都在其中);本站不把这类差异算作地区规范差异。完整取舍见 [数据规则与已知限制](docs/known-issues.md)。
+最终判定取**黑体与宋体结果的并集**:只要其中一款把两地画成同一字形,本应用就按同形处理。这能排除只出现在单款字体中的设计细节。例如 Source Han Sans 为约五分之一的日本常用汉字提供独立字形,其中约两百个在 Source Han Serif 并未区分(了、人、子、水、金都在其中);本应用不把这类差异算作地区规范差异。完整取舍见 [数据规则与已知限制](docs/known-issues.md)。
页面会按这份判定重新分组:被判为同形的格子统一借用组内一个地区的 Noto 字体,使屏幕上也真正呈现同一轮廓。相应地,被上述规则过滤的地区版本细小差异不会显示。`scripts/tests/fonts.test.ts` 会用 fontkit 取出生成字体的真实轮廓,逐字验证判定与画面显示一致。
## 局限
-- 本站只比较通用黑体与宋体中的印刷字形,不涵盖手写习惯,也不以教科书体的示范字形为准。日语教科书体主要为日语教学设计,并没有与中、港、台、韩共享同一字形池的正式地区版本;若拼接风格相近但来源不同的字体,地区差异与字体自身的设计差异就无法分开。为了控制变量,本站只能选用同时提供五地版本的同源字体系列。
+- 本应用只比较通用黑体与宋体中的印刷字形,不涵盖手写习惯,也不以教科书体的示范字形为准。日语教科书体主要为日语教学设计,并没有与中、港、台、韩共享同一字形池的正式地区版本;若拼接风格相近但来源不同的字体,地区差异与字体自身的设计差异就无法分开。为了控制变量,本应用只能选用同时提供五地版本的同源字体系列。
- 判定的对象是 Source Han 的地区字形设计,不是各地标准本身。它是个高质量的代理——Adobe 的地区字形分别依据大陆《印刷通用汉字字形表》、台湾教育部《國字標準字體》、香港教育局《常用字字形表》、日本 JIS X 0208/0213、韩国 KS X 1001/1002。
- Source Han 的香港字形覆盖并不完整,「只有香港不同」这一类可能少报。
- 笔画数逐地区取:先看 Unihan 的 `kAlternateTotalStrokes`,日本再退到 `kRSAdobe_Japan1_6`(它分析的是 Adobe-Japan1-6 收的日本字形,所以 突 是 8 画而不是 9 画),都没有才用 `kTotalStrokes`。后者只区分简繁两档,港台日韩通常共用繁体值;例如 那 的五列笔画数是 6/6/6/7/6。
@@ -53,9 +53,9 @@
## 声明
-本站是基于公开资料制作的字形对照工具,不是各地的规范、词典或教学材料。页面展示的是本站采用的数据与自动规则所得的结果,不能据此断定某个字在当地只有这一种“正确”形式。
+本应用是基于公开资料制作的字形对照工具,不是各地的规范、词典或教学材料。页面展示的是本应用采用的数据与自动规则所得的结果,不能据此断定某个字在当地只有这一种“正确”形式。
-各地规范的适用范围和定义并不完全相同,字体也只是规范的一种设计实现。本站会整理、转换并合并不同来源的数据,也会为未收录项补上参考字形;这些都是工程取舍,难免带来简化、遗漏与错误。
+各地规范的适用范围和定义并不完全相同,字体也只是规范的一种设计实现。本应用会整理、转换并合并不同来源的数据,也会为未收录项补上参考字形;这些都是工程取舍,难免带来简化、遗漏与错误。
页面中的“同形”或“不同”只在上述资料、字体与规则内成立;地区的排列与分组只为方便对照,不表示优劣或立场。正式场合请以原始规范与词典为准;每个字的详情页都列有相应地区的字典链接。汉字数量巨大,出错难免,发现数据有误请提 [issue](https://github.com/sxzz/hanji/issues)。
@@ -67,16 +67,16 @@ pnpm build:data # 生成字表与字体子集,首次会下载约 261 MiB 原
pnpm update:sources # 检查并锁定新版第三方数据;有变化时下载并重新生成
pnpm dev
pnpm test
-pnpm generate # 静态站点
+pnpm generate # 静态应用
```
每一行的地址是它的行名(`/char/着`)。五地展示形、`aka` 和 `alternatives` 也可作为地址,由客户端跳到所属的行——例如 `/char/国`、`/char/郞`、`/char/缐`,页面用 `rel=canonical` 指回行名地址;未确认关系不是地址别名。
-字表 `public/data/chars.json` 和来源清单 `public/data/sources.json` 都由 `pnpm build:dataset` 生成,**不提交**;构建后仍分别作为 `/data/chars.json` 与 `/data/sources.json` 开放下载。所有 `/data/*.json` 响应均允许任意来源跨域读取,并要求缓存重新验证,避免稳定 URL 留下旧数据。字体子集约 12MB,同样不提交,由 `pnpm build:data` 生成——所以构建前必须先跑一次。原始下载缓存在 `data/raw/` 下按类别存放(`charlist/`、`opencc/`、`cmap/`、`font/`、`unihan/`、`frequency/`、`strokes/`),已 gitignore;构建会清理从旧缓存恢复、但已不在当前来源清单中的文件。
+字表 `app/assets/data/chars.json` 由 `pnpm build:dataset` 生成,**不提交**;应用直接导入它,Vite 也会输出带内容哈希的下载地址。静态生成结束后,字表会额外复制到固定的 `/data/chars.json` 供外部网站引用,“关于”页链接的也是这个地址;它缓存 1 小时,过期后可在后台重新验证期间继续使用旧版本 1 天。来源与许可元数据直接维护在 `shared/sources.ts`,由页面和构建脚本共同导入,不再生成 `sources.json`。约 12MB 的字体子集生成到 `app/assets/fonts/`,同样不提交;字表、笔顺、字体和旗帜都进入 Vite 资源图,由 `/_nuxt/*` 的长期 immutable 缓存安全复用。会变动而又需要稳定 URL 的 NOTICE 与 license 文本单独放在 `public/notices/`,每次使用前必须重新验证。构建前需先运行 `pnpm build:data`。原始下载缓存在 `data/raw/` 下按类别存放(`charlist/`、`opencc/`、`cmap/`、`font/`、`unihan/`、`frequency/`、`strokes/`),已 gitignore;构建会清理从旧缓存恢复、但已不在当前来源清单中的文件。
## 部署
-静态站点,`.output/public` 直接丢给任意静态托管即可。线上部署由 [GitHub Actions](.github/workflows/deploy.yml) 构建后直传 Cloudflare Workers Static Assets:
+这是静态应用,`.output/public` 直接交给任意静态托管即可。线上部署由 [GitHub Actions](.github/workflows/deploy.yml) 构建后直传 Cloudflare Workers Static Assets:
- `main` 的 push 部署到 production;
- 指向 `main` 的 PR 通过 `wrangler versions upload` 部署到 `pr-<编号>` preview alias,GitHub 会在 PR 中显示对应的 deployment 与访问地址,后续提交沿用同一个预览地址。
@@ -92,11 +92,11 @@ Cloudflare Worker 名称须为 `hanji`,与 `wrangler.json` 中的 `name` 一
需要页面访问量和 Web Vitals 时,请在实际域名所属账户的 **Web Analytics → Add a site** 中选择已由 Cloudflare 代理的 hostname,并使用 automatic setup。Cloudflare 会在边缘自动注入 beacon。
-每个字组详情页都会生成独立 HTML;页面数据在本地 bundle 中,因此关闭了每路由额外生成 `_payload.json` 的 payload extraction。地区异体别名不另外生成跳转页:它先由 Static Assets 返回 `404.html` 和 HTTP 404,再由 Nuxt 客户端中间件跳到所属行;搜索引擎不会把 alias 当作成功页面重复收录。真正未知的地址保持 HTTP 404;`public/_headers` 给带内容哈希的 `_nuxt/*` 设了长缓存。
+每个字组详情页都会生成独立 HTML;页面数据在本地 bundle 中,因此关闭了每路由额外生成 `_payload.json` 的 payload extraction。地区异体别名不另外生成跳转页:它先由 Static Assets 返回 `404.html` 和 HTTP 404,再由 Nuxt 客户端中间件跳到所属行;搜索引擎不会把 alias 当作成功页面重复收录。真正未知的地址保持 HTTP 404;`public/_headers` 给带内容哈希的 `_nuxt/*` 设长期 immutable 缓存,让稳定的 `/notices/*` URL 使用 `no-cache`,并为 `/data/chars.json` 设置 1 小时的 `max-age` 与 1 天的 `stale-while-revalidate`。
第三方资产的具体 commit、官方附件标识与 SHA-256 记录在 `data/sources.lock.json`;需要升级时运行 `pnpm update:sources`。它会解析有版本上游的版本号,并重新校验没有版本号的官方直链;内容有变化时更新 lockfile 并直接重新生成数据,完全未变则跳过生成。构建时 `pnpm build:data` 会按 lockfile 下载并校验约 **261 MiB** 原始数据(其中 195 MiB 是十份 Noto CJK 字体);任何未显式更新的直链内容变化都会因校验和不符而失败,不会静默进入数据。Actions 分开缓存原始下载与生成字体:前者只由 lockfile 决定,后者由 lockfile、实际生成脚本、相关依赖、locale 与字表决定;字体输入完全不变时跳过数据生成。
-笔顺分片和随附授权由 `pnpm build:dataset` 生成到 `public/strokes/`,不提交到仓库;部署流程会在测试和静态生成前重建它们。同一字组内,按笔画顺序排列的轮廓完全一致时只保存第一份变体及其中线,界面也把对应地区合并为一个选择项。笔顺分片使用稳定路径,不设置专用缓存策略。
+笔顺分片由 `pnpm build:dataset` 生成到 `app/assets/strokes/`,不提交到仓库;部署流程会在测试和静态生成前重建,再由 Vite 输出带内容哈希的文件名。随附授权保留在 `public/notices/` 的稳定 URL 下并要求重新验证。同一字组内,按笔画顺序排列的轮廓完全一致时只保存第一份变体及其中线,界面也把对应地区合并为一个选择项;页面加载时只获取一次所属分片,之后切换地区直接复用内存中的字组数据。
本地也可构建后直传:
@@ -130,7 +130,7 @@ pnpm deploy
逐字对照工具 [tofu.tools](https://tofu.tools/) 是本项目的先行者,同样用 Noto 系列区分地区字形。
-字体为 Noto Sans CJK 与 Noto Serif CJK(SIL OFL 1.1)按本站用字子集化后的产物,声明随附于 `/fonts/OFL.txt`。生成的数据文件派生自上述来源,请遵守各自许可;逐项转换方式与署名也写入公开的 [`/data/NOTICE.md`](public/data/NOTICE.md)。
+字体为 Noto Sans CJK 与 Noto Serif CJK(SIL OFL 1.1)按本应用用字子集化后的产物,声明随附于 [`/notices/noto-ofl.txt`](public/notices/noto-ofl.txt)。生成的数据文件派生自上述来源,请遵守各自许可;逐项转换方式与署名也写入公开的 [`/notices/data-sources.md`](public/notices/data-sources.md)。
## License
diff --git a/app/app.vue b/app/app.vue
index 7657766..9349871 100644
--- a/app/app.vue
+++ b/app/app.vue
@@ -1,6 +1,7 @@
diff --git a/public/flags/cn.svg b/app/assets/flags/cn.svg
similarity index 100%
rename from public/flags/cn.svg
rename to app/assets/flags/cn.svg
diff --git a/public/flags/hk.svg b/app/assets/flags/hk.svg
similarity index 100%
rename from public/flags/hk.svg
rename to app/assets/flags/hk.svg
diff --git a/public/flags/jp.svg b/app/assets/flags/jp.svg
similarity index 100%
rename from public/flags/jp.svg
rename to app/assets/flags/jp.svg
diff --git a/public/flags/kr.svg b/app/assets/flags/kr.svg
similarity index 100%
rename from public/flags/kr.svg
rename to app/assets/flags/kr.svg
diff --git a/public/flags/tw.svg b/app/assets/flags/tw.svg
similarity index 100%
rename from public/flags/tw.svg
rename to app/assets/flags/tw.svg
diff --git a/app/components/DataSources.vue b/app/components/DataSources.vue
index baab0dd..554478e 100644
--- a/app/components/DataSources.vue
+++ b/app/components/DataSources.vue
@@ -1,11 +1,9 @@
('idle')
const displayedChoice = shallowRef()
+const loadedGroup = shallowRef<{
+ key: string
+ data: PackedStrokeGroup
+}>()
const strokes = shallowRef([])
const viewBox = ref(ANIMCJK_VIEW_BOX)
const strokeTransform = ref()
@@ -55,7 +60,7 @@ const pathElements: SVGPathElement[] = []
const clipPrefix = useId().replaceAll(':', '')
let mounted = false
-let request: AbortController | undefined
+let requestVersion = 0
let animation: Animation | undefined
let run = 0
@@ -182,24 +187,46 @@ async function measure() {
)
}
-function loadChoice(
- choice: StrokeOrderChoice,
- signal: AbortSignal,
-): Promise {
- return loadAnimCJK(choice.variant, choice.groupKey, signal)
-}
+async function displayChoice(choice: StrokeOrderChoice) {
+ const data: StrokeAnimationData | undefined = unpackAnimCJK(
+ loadedGroup.value?.data.variants[choice.variant],
+ )
+ if (!data) throw new Error('declared stroke data is missing')
-async function load() {
- request?.abort()
- stop()
cancelSkeletonReveal()
+ isLoading.value = false
+ showSkeleton.value = false
+ stop()
+ pathElements.length = 0
+ completed.value = 0
+ strokes.value = data.strokes
+ viewBox.value = data.viewBox
+ strokeTransform.value = data.transform
+ revealWidth.value = data.revealWidth ?? ANIMCJK_REVEAL_WIDTH
+ displayedChoice.value = choice
+ status.value = 'ready'
+ await measure()
+}
+async function load() {
+ const version = ++requestVersion
const choice = currentChoice.value
if (!choice) {
isLoading.value = false
return
}
+ if (loadedGroup.value?.key === choice.groupKey) {
+ try {
+ await displayChoice(choice)
+ } catch {
+ status.value = 'error'
+ }
+ return
+ }
+
+ stop()
+ cancelSkeletonReveal()
const keepCurrent =
status.value === 'ready' && displayedChoice.value !== undefined
isLoading.value = true
@@ -211,32 +238,20 @@ async function load() {
showSkeleton.value = true
}
- const controller = new AbortController()
- request = controller
try {
- const data = await loadChoice(choice, controller.signal)
- if (controller.signal.aborted) return
- if (!data) throw new Error('declared stroke data is missing')
+ const data = await loadStrokeGroup(choice.groupKey)
+ if (version !== requestVersion) return
+ if (!data) throw new Error('declared stroke group is missing')
+ loadedGroup.value = { key: choice.groupKey, data }
+ const latest = currentChoice.value
+ if (!latest || latest.groupKey !== choice.groupKey) return
+ await displayChoice(latest)
+ } catch {
+ if (version !== requestVersion) return
cancelSkeletonReveal()
isLoading.value = false
showSkeleton.value = false
- stop()
- pathElements.length = 0
- completed.value = 0
- strokes.value = data.strokes
- viewBox.value = data.viewBox
- strokeTransform.value = data.transform
- revealWidth.value = data.revealWidth ?? ANIMCJK_REVEAL_WIDTH
- displayedChoice.value = choice
- status.value = 'ready'
- await measure()
- } catch (error) {
- if (!(error instanceof DOMException) || error.name !== 'AbortError') {
- cancelSkeletonReveal()
- isLoading.value = false
- showSkeleton.value = false
- status.value = 'error'
- }
+ status.value = 'error'
}
}
@@ -333,7 +348,7 @@ onMounted(() => {
load()
})
onBeforeUnmount(() => {
- request?.abort()
+ requestVersion++
cancelSkeletonReveal()
stop()
})
diff --git a/app/composables/chars.ts b/app/composables/chars.ts
index 1e9134e..4e89daa 100644
--- a/app/composables/chars.ts
+++ b/app/composables/chars.ts
@@ -1,4 +1,3 @@
-import charsRaw from '~~/public/data/chars.json?raw'
import { frequencyRankOf } from '~~/shared/frequency.ts'
import { createListingMatcher, listingOptionsFor } from '~~/shared/listings.ts'
import {
@@ -19,6 +18,7 @@ import {
type CharsData,
type Region,
} from '~~/shared/types.ts'
+import charsRaw from '~/assets/data/chars.json?raw'
import { LOCALE_FREQUENCY_REGION } from '~/locales/index.ts'
import { isUnicodeScalarValue } from '~/utils/unicode.ts'
import { useColumnVisibility } from './prefs.ts'
diff --git a/app/composables/overprint.ts b/app/composables/overprint.ts
index 0439e3b..11c883d 100644
--- a/app/composables/overprint.ts
+++ b/app/composables/overprint.ts
@@ -59,7 +59,7 @@ export interface OverprintOptions {
}
/**
- * The behavior shared by every overprint on the site: the hero, the row
+ * The behavior shared by every overprint in the app: the hero, the row
* thumbnails and the character being compared on a detail page.
*
* A stack reads as one picture, which tells the reader nothing about how many
diff --git a/app/locales/ja-jp.ts b/app/locales/ja-jp.ts
index 83838a2..b74a0c2 100644
--- a/app/locales/ja-jp.ts
+++ b/app/locales/ja-jp.ts
@@ -32,7 +32,7 @@ export const jaJP: typeof zhCN = {
detail: '詳細を見る',
},
nav: {
- about: 'このサイトについて',
+ about: 'アプリについて',
github: 'GitHubリポジトリ',
theme: '表示テーマを切り替える',
back: '漢字表に戻る',
@@ -141,7 +141,7 @@ export const jaJP: typeof zhCN = {
tierKr: { 1: '漢文教育用基礎漢字' },
},
about: {
- title: 'このサイトについて',
+ title: 'アプリについて',
nameTitle: '名前の由来',
name1:
'「Hanji」は、各地における漢字の呼び名と、それぞれ一文字ずつ異なります。中国語ピンインのhanziとは「j/z」、日本語のkanjiとは「h/k」、韓国語のhanjaとは「i/a」が違います。同じものでも地域ごとに少しずつ変わる――それこそが、このアプリが扱うテーマです。',
@@ -160,7 +160,7 @@ export const jaJP: typeof zhCN = {
method1:
'判定にはAdobeのSource Han SansとSource Han Serifを使い、画面表示にはそれらと同じソースから作られたNoto Sans CJKとNoto Serif CJKを使います。各書体は中・港・台・日・韓の5地域で一つの字形プールを共有し、地域ごとに「Unicodeコードポイント→字形番号(CID)」の対応表を持ちます。2地域が同じCIDに対応すれば、同形と判定します。',
method2:
- '最終判定にはゴシック体と明朝体の結果の和集合を使います。どちらか一方でも2地域が同じ字形なら、本サイトでは同形と扱います。これにより、一方の書体だけに現れるデザイン上の細部を除外できます。例えばSource Han Sansは日本の常用漢字の約5分の1に独自字形を用意していますが、そのうち約200字はSource Han Serifでは区別されていません。「了」「人」「子」「水」「金」などが該当し、本サイトではこのような差を地域規範の差とはみなしません。',
+ '最終判定にはゴシック体と明朝体の結果の和集合を使います。どちらか一方でも2地域が同じ字形なら、本アプリでは同形と扱います。これにより、一方の書体だけに現れるデザイン上の細部を除外できます。例えばSource Han Sansは日本の常用漢字の約5分の1に独自字形を用意していますが、そのうち約200字はSource Han Serifでは区別されていません。「了」「人」「子」「水」「金」などが該当し、本アプリではこのような差を地域規範の差とはみなしません。',
method3:
'ページはこの判定に従ってセルを再グループ化します。同形と判定されたセルは、同じグループの一地域のNotoフォントを共通で使うため、画面上でも実際に同じ輪郭になります。その代わり、特定の地域版だけが持つ小さな差が上記のルールで除外された場合、その差は表示されません。',
scope1:
@@ -170,7 +170,7 @@ export const jaJP: typeof zhCN = {
scope3:
'台湾の『次常用國字表』は既存行の二級収録区分と候補だけを補い、新しい行は生成しません。6,343の主項目のうち、この表にしかない3,599項目は明示的に製品の収録範囲外です。',
limitPrint:
- '本サイトが扱うのは汎用的なゴシック体・明朝体の印刷字形であり、手書きの慣習や教科書体の例示字形は対象外です。日本語の教科書体は主に日本語教育用に設計されており、中国大陸・香港・台湾・韓国と同じ字形プールを共有する正式な地域版はありません。見た目が近くても出自の異なる書体を混ぜると、地域差と書体自体のデザイン差を切り分けられないため、本サイトでは5地域版を揃える同一系統の書体だけを比較できます。',
+ '本アプリが扱うのは汎用的なゴシック体・明朝体の印刷字形であり、手書きの慣習や教科書体の例示字形は対象外です。日本語の教科書体は主に日本語教育用に設計されており、中国大陸・香港・台湾・韓国と同じ字形プールを共有する正式な地域版はありません。見た目が近くても出自の異なる書体を混ぜると、地域差と書体自体のデザイン差を切り分けられないため、本アプリでは5地域版を揃える同一系統の書体だけを比較できます。',
limit1:
'ここで測定しているのはSource Hanシリーズの地域別字形デザインであり、各地域の規格そのものではありません。それでも精度の高い代替指標です。Adobeの地域別字形はそれぞれ、中国大陸の『印刷通用漢字字形表』、台湾教育部の『國字標準字體』、香港教育局の『常用字字形表』、日本のJIS X 0208/0213(JIS2004字形)、韓国のKS X 1001/1002に基づいているためです。',
limit2:
@@ -184,14 +184,14 @@ export const jaJP: typeof zhCN = {
limit6:
'各地域のセルは、字表の主項目、括注異体字、未収録時の代替表示を区別します。未収録のセルにも伝承された参考字形を表示します。これらの状態は詳細ページだけで説明し、一覧には印を追加しません。',
notice1:
- '本サイトは公開資料に基づく字形比較ツールであり、各地域の規格、辞書、教材そのものではありません。表示内容は、本サイトが採用するデータと自動ルールから得た結果です。これを根拠に、ある漢字がその地域で唯一の「正しい」形だと判断することはできません。',
+ '本アプリは公開資料に基づく字形比較ツールであり、各地域の規格、辞書、教材そのものではありません。表示内容は、本アプリが採用するデータと自動ルールから得た結果です。これを根拠に、ある漢字がその地域で唯一の「正しい」形だと判断することはできません。',
notice2:
- '地域ごとの規格は適用範囲や定義が完全には一致せず、フォントも規格を実装する一つのデザインにすぎません。本サイトでは異なるソースのデータを整理・変換・統合し、未収録項目には参考字形も補います。これらはすべて技術的な取捨選択であり、簡略化、見落とし、誤りを完全には避けられません。',
+ '地域ごとの規格は適用範囲や定義が完全には一致せず、フォントも規格を実装する一つのデザインにすぎません。本アプリでは異なるソースのデータを整理・変換・統合し、未収録項目には参考字形も補います。これらはすべて技術的な取捨選択であり、簡略化、見落とし、誤りを完全には避けられません。',
notice3:
'ページ上の「同形」や「異なる」という表示は、上記の資料、フォント、ルールの範囲内でのみ成り立ちます。地域の並び順やグループ分けは比較の便宜のためであり、優劣や立場を示すものではありません。正式な用途では元の規格や辞書を参照してください。各漢字の詳細ページには、対応する地域の辞書へのリンクを掲載しています。',
notice4:
'漢字は膨大なため、誤りを完全には避けられません。データの誤りを見つけた場合は{issues}でお知らせください。',
- data1: '本サイトの漢字表データはJSON形式で公開しています:',
+ data1: '本アプリの漢字表データはJSON形式で公開しています:',
thanks:
'OpenCC、Unicode Unihan、zispace/hanzi-chars、Jun Da、words.hk、国家教育研究院、scriptin/kanji-frequency、Adobe Source Han、Noto CJKのメンテナーの皆さまに感謝します。一字ずつ比較できる{tofu}は本プロジェクトの先駆けであり、同じくNotoシリーズを使って地域別字形を区別しています。',
},
diff --git a/app/locales/ko-kr.ts b/app/locales/ko-kr.ts
index 09f9bc4..d431e77 100644
--- a/app/locales/ko-kr.ts
+++ b/app/locales/ko-kr.ts
@@ -32,7 +32,7 @@ export const koKR: typeof zhCN = {
detail: '자세히 보기',
},
nav: {
- about: '사이트 소개',
+ about: '앱 소개',
github: 'GitHub 저장소',
theme: '밝은/어두운 테마 전환',
back: '한자표로 돌아가기',
@@ -141,7 +141,7 @@ export const koKR: typeof zhCN = {
tierKr: { 1: '한문 교육용 기초 한자' },
},
about: {
- title: '사이트 소개',
+ title: '앱 소개',
nameTitle: '이름의 유래',
name1:
'“Hanji”는 지역별 한자의 이름과 각각 알파벳 한 글자씩 다릅니다. 중국어 병음 hanzi와는 j/z, 일본어 kanji와는 h/k, 한국어 hanja와는 i/a가 다릅니다. 같은 것이라도 지역마다 조금씩 달라진다는 점이 바로 이 앱의 주제입니다.',
@@ -160,17 +160,17 @@ export const koKR: typeof zhCN = {
method1:
'판정에는 Adobe Source Han Sans와 Source Han Serif를 사용하고, 화면에는 이들과 같은 소스로 제작된 Noto Sans CJK와 Noto Serif CJK로 결과를 표시합니다. 각 글꼴은 중국 대륙·홍콩·대만·일본·한국 다섯 지역이 하나의 자형 풀을 공유하고, 지역별로 ‘Unicode 코드 포인트 → 자형 번호(CID)’ 매핑을 제공합니다. 두 지역이 같은 CID에 매핑되면 같은 자형으로 판정합니다.',
method2:
- '최종 판정은 고딕체와 명조체 결과의 합집합을 사용합니다. 둘 중 하나라도 두 지역을 같은 자형으로 표현하면 이 사이트에서는 같은 자형으로 처리합니다. 이렇게 하면 한 글꼴에서만 나타나는 디자인 세부 차이를 제외할 수 있습니다. 예를 들어 Source Han Sans는 일본 상용한자의 약 5분의 1에 독자 자형을 제공하지만, 그중 약 200자는 Source Han Serif에서 구분되지 않습니다. ‘了’, ‘人’, ‘子’, ‘水’, ‘金’ 등이 여기에 해당하며, 이 사이트는 이런 차이를 지역 규범의 차이로 보지 않습니다.',
+ '최종 판정은 고딕체와 명조체 결과의 합집합을 사용합니다. 둘 중 하나라도 두 지역을 같은 자형으로 표현하면 이 앱에서는 같은 자형으로 처리합니다. 이렇게 하면 한 글꼴에서만 나타나는 디자인 세부 차이를 제외할 수 있습니다. 예를 들어 Source Han Sans는 일본 상용한자의 약 5분의 1에 독자 자형을 제공하지만, 그중 약 200자는 Source Han Serif에서 구분되지 않습니다. ‘了’, ‘人’, ‘子’, ‘水’, ‘金’ 등이 여기에 해당하며, 이 앱은 이런 차이를 지역 규범의 차이로 보지 않습니다.',
method3:
'페이지는 이 판정에 따라 칸을 다시 묶습니다. 같은 자형으로 판정된 칸은 같은 그룹 내 한 지역의 Noto 글꼴을 공통으로 사용하므로 화면에서도 실제로 같은 윤곽으로 표시됩니다. 대신 특정 지역판에만 있는 작은 차이가 위 규칙에서 제외되면 그 차이는 표시되지 않습니다.',
scope1:
'중국 대륙의 《통용규범한자표》(通用規範漢字表, 2013), 대만의 《상용국자표준자체표》(常用國字標準字體表, 1982), 홍콩의 《상용자자형표》(常用字字形表), 일본의 《상용한자표》(常用漢字表, 2010), 한국의 《한문교육용기초한자》(漢文教育用基礎漢字, 2000) 다섯 목록의 합집합을 수록합니다. 간체자와 번체자, 일본 신자체와 구자체처럼 코드 포인트가 다른 대응 글자는 한 행으로 합쳐 총 {rows}행입니다. 한국어 화면에서는 한국 열이 기본적으로 표시되며 표시 설정에서 끈 수 있습니다.',
scope2:
- '이 가운데 {identical}행은 다섯 지역의 자형이 완전히 같고, {allDiffer}행은 다섯 지역이 모두 다릅니다. 자형이 같은 한자도 수록합니다. 이 사이트는 다섯 지역의 한자표이며, 자형 차이는 여러 비교 기준 중 하나일 뿐입니다.',
+ '이 가운데 {identical}행은 다섯 지역의 자형이 완전히 같고, {allDiffer}행은 다섯 지역이 모두 다릅니다. 자형이 같은 한자도 수록합니다. 이 앱은 다섯 지역의 한자표이며, 자형 차이는 여러 비교 기준 중 하나일 뿐입니다.',
scope3:
'대만의 《차상용국자표》(次常用國字表)는 기존 행의 2급 수록 상태와 후보만 보완하며 새 행을 만들지는 않습니다. 6,343개의 주 항목 가운데 이 표에만 있는 3,599개 항목은 제품 수록 범위에서 명시적으로 제외합니다.',
limitPrint:
- '이 사이트는 범용 고딕체·명조체의 인쇄 자형만 다루며, 손글씨 관습이나 교과서체의 본보기 자형은 다루지 않습니다. 일본어 교과서체는 주로 일본어 교육용으로 설계되었으며, 중국 대륙·홍콩·대만·한국과 같은 자형 풀을 공유하는 공식 지역판이 없습니다. 보기에 비슷해도 출처가 다른 글꼴을 섞으면 지역 차이와 글꼴 자체의 디자인 차이를 구분할 수 없으므로, 이 사이트는 다섯 지역판을 함께 제공하는 동일 계열만 비교할 수 있습니다.',
+ '이 앱은 범용 고딕체·명조체의 인쇄 자형만 다루며, 손글씨 관습이나 교과서체의 본보기 자형은 다루지 않습니다. 일본어 교과서체는 주로 일본어 교육용으로 설계되었으며, 중국 대륙·홍콩·대만·한국과 같은 자형 풀을 공유하는 공식 지역판이 없습니다. 보기에 비슷해도 출처가 다른 글꼴을 섞으면 지역 차이와 글꼴 자체의 디자인 차이를 구분할 수 없으므로, 이 앱은 다섯 지역판을 함께 제공하는 동일 계열만 비교할 수 있습니다.',
limit1:
'여기서 측정하는 것은 Source Han 계열의 지역별 자형 디자인이지 각 지역의 표준 자체가 아닙니다. 그래도 정확도가 높은 대리 지표입니다. Adobe의 지역별 자형이 각각 중국 대륙의 《인쇄통용한자자형표》(印刷通用漢字字形表), 대만 교육부의 《국자표준자체》(國字標準字體), 홍콩 교육국의 《상용자자형표》(常用字字形表), 일본의 JIS X 0208/0213(JIS2004 자형), 한국의 KS X 1001/1002를 바탕으로 하기 때문입니다.',
limit2:
@@ -184,14 +184,14 @@ export const koKR: typeof zhCN = {
limit6:
'각 지역 칸은 한자표의 주 항목, 괄호 병기 이체자, 미수록 시 대체 표시를 구분합니다. 미수록 칸에도 전승된 참고 자형을 표시합니다. 이러한 상태는 상세 페이지에서만 설명하고 목록에는 별도 표시를 추가하지 않습니다.',
notice1:
- '이 사이트는 공개 자료를 바탕으로 만든 자형 비교 도구이며, 각 지역의 규범·사전·교육 자료 자체가 아닙니다. 화면에 나타나는 내용은 이 사이트가 채택한 데이터와 자동 규칙으로 얻은 결과이므로, 특정 한자가 해당 지역에서 유일한 ‘올바른’ 형태라고 단정할 수 없습니다.',
+ '이 앱은 공개 자료를 바탕으로 만든 자형 비교 도구이며, 각 지역의 규범·사전·교육 자료 자체가 아닙니다. 화면에 나타나는 내용은 이 앱이 채택한 데이터와 자동 규칙으로 얻은 결과이므로, 특정 한자가 해당 지역에서 유일한 ‘올바른’ 형태라고 단정할 수 없습니다.',
notice2:
- '지역별 규범은 적용 범위와 정의가 완전히 같지 않고, 글꼴도 규범을 구현한 하나의 디자인에 불과합니다. 이 사이트는 여러 출처의 데이터를 정리·변환·통합하고, 미수록 항목에는 참고 자형을 보충합니다. 이러한 과정은 모두 기술적 판단이며 단순화·누락·오류를 완전히 피할 수 없습니다.',
+ '지역별 규범은 적용 범위와 정의가 완전히 같지 않고, 글꼴도 규범을 구현한 하나의 디자인에 불과합니다. 이 앱은 여러 출처의 데이터를 정리·변환·통합하고, 미수록 항목에는 참고 자형을 보충합니다. 이러한 과정은 모두 기술적 판단이며 단순화·누락·오류를 완전히 피할 수 없습니다.',
notice3:
'페이지의 ‘같은 자형’ 또는 ‘다른 자형’ 표시는 위 자료·글꼴·규칙의 범위 안에서만 성립합니다. 지역의 나열과 그룹화는 비교를 편리하게 하기 위한 것으로, 우열이나 특정한 입장을 나타내지 않습니다. 공식적인 용도에는 원래 규범이나 사전을 참고하세요. 각 한자의 상세 페이지에는 해당 지역 사전으로 가는 링크가 있습니다.',
notice4:
'한자는 수가 방대해 오류를 완전히 피하기 어렵습니다. 데이터 오류를 발견하면 {issues}로 알려 주세요.',
- data1: '이 사이트의 한자표 데이터는 JSON 형식으로 공개합니다:',
+ data1: '이 앱의 한자표 데이터는 JSON 형식으로 공개합니다:',
thanks:
'OpenCC, Unicode Unihan, zispace/hanzi-chars, Jun Da, words.hk, 국가교육연구원, scriptin/kanji-frequency, Adobe Source Han, Noto CJK 유지 관리자 여러분께 감사드립니다. 한 글자씩 비교할 수 있는 {tofu}는 이 프로젝트에 앞서 같은 방식으로 Noto 계열을 사용해 지역별 자형을 구분한 도구입니다.',
},
diff --git a/app/locales/zh-cn.ts b/app/locales/zh-cn.ts
index 620d757..47a4cd3 100644
--- a/app/locales/zh-cn.ts
+++ b/app/locales/zh-cn.ts
@@ -155,7 +155,7 @@ export const zhCN = {
method1:
'判定依据Adobe Source Han Sans与Source Han Serif;页面则使用与它们同源的Noto Sans CJK与Noto Serif CJK显示结果。每套字体让中、港、台、日、韩五个地区共用一个字形池,并分别提供「Unicode码点→字形编号(CID)」映射。两地映射到同一CID,就视为同形。',
method2:
- '最终判定取黑体与宋体结果的并集:只要其中一款把两地画成同一字形,本站就按同形处理。这能排除只出现在单款字体中的设计细节。例如Source Han Sans为约五分之一的日本常用汉字提供独立字形,其中约两百个在Source Han Serif并未区分,像了、人、子、水、金;本站不把这类差异算作地区规范差异。',
+ '最终判定取黑体与宋体结果的并集:只要其中一款把两地画成同一字形,本应用就按同形处理。这能排除只出现在单款字体中的设计细节。例如Source Han Sans为约五分之一的日本常用汉字提供独立字形,其中约两百个在Source Han Serif并未区分,像了、人、子、水、金;本应用不把这类差异算作地区规范差异。',
method3:
'页面会按这份判定重新分组:被判为同形的格子统一借用组内一个地区的Noto字体,因此屏幕上也会真正呈现同一轮廓。相应地,某个地区版本独有、但被上述规则过滤的细小差异不会显示。',
scope1:
@@ -165,7 +165,7 @@ export const zhCN = {
scope3:
'台湾《次常用国字表》只为已有行提供二级收录状态和候选,不参与生成新行;其6,343个主条目中有3,599个独有条目明确在产品范围之外。',
limitPrint:
- '本站只比较通用黑体与宋体中的印刷字形,不涵盖手写习惯,也不以教科书体的示范字形为准。日语教科书体主要为日语教学设计,并没有与中、港、台、韩共享同一字形池的正式地区版本;若拼接风格相近但来源不同的字体,地区差异与字体自身的设计差异就无法分开。为了控制变量,本站只能选用同时提供五地版本的同源字体系列。',
+ '本应用只比较通用黑体与宋体中的印刷字形,不涵盖手写习惯,也不以教科书体的示范字形为准。日语教科书体主要为日语教学设计,并没有与中、港、台、韩共享同一字形池的正式地区版本;若拼接风格相近但来源不同的字体,地区差异与字体自身的设计差异就无法分开。为了控制变量,本应用只能选用同时提供五地版本的同源字体系列。',
limit1:
'这里测的是Source Han系列的地区字形设计,不是各地标准本身。它是个高质量的代理,因为Adobe的地区字形分别依据大陆《印刷通用汉字字形表》、台湾教育部《國字標準字體》、香港教育局《常用字字形表》、日本JIS X 0208/0213(JIS2004字形)与韩国KS X 1001/1002。',
limit2:
@@ -179,13 +179,13 @@ export const zhCN = {
limit6:
'每个地区格会区分字表主条目、括注异体和未收录回退。未收录格仍显示传承参考字形;这些状态只在详情页说明,不在列表页增加角标。',
notice1:
- '本站是基于公开资料制作的字形对照工具,不是各地的规范、词典或教学材料。页面展示的是本站采用的数据与自动规则所得的结果,不能据此断定某个字在当地只有这一种「正确」形式。',
+ '本应用是基于公开资料制作的字形对照工具,不是各地的规范、词典或教学材料。页面展示的是本应用采用的数据与自动规则所得的结果,不能据此断定某个字在当地只有这一种「正确」形式。',
notice2:
- '各地规范的适用范围和定义并不完全相同,字体也只是规范的一种设计实现。本站会整理、转换并合并不同来源的数据,也会为未收录项补上参考字形;这些都是工程取舍,难免带来简化、遗漏与错误。',
+ '各地规范的适用范围和定义并不完全相同,字体也只是规范的一种设计实现。本应用会整理、转换并合并不同来源的数据,也会为未收录项补上参考字形;这些都是工程取舍,难免带来简化、遗漏与错误。',
notice3:
'页面中的「同形」或「不同」只在上述资料、字体与规则内成立;地区的排列与分组只为方便对照,不表示优劣或立场。正式场合请以原始规范与词典为准;每个字的详情页都列有相应地区的字典链接。',
notice4: '汉字数量巨大,出错难免。发现数据有误,请通过{issues}反馈。',
- data1: '本站的字表数据以JSON提供,可直接取用:',
+ data1: '本应用的字表数据以JSON提供,可直接取用:',
thanks:
'感谢OpenCC、Unicode Unihan、zispace/hanzi-chars、Jun Da、粤典、国家教育研究院、scriptin/kanji-frequency、Adobe Source Han与Noto CJK的维护者。逐字对照工具{tofu}是本项目的先行者,同样用Noto系列区分地区字形。',
},
diff --git a/app/locales/zh-hk.ts b/app/locales/zh-hk.ts
index cbed3c0..5a495dc 100644
--- a/app/locales/zh-hk.ts
+++ b/app/locales/zh-hk.ts
@@ -156,7 +156,7 @@ export const zhHK: typeof zhCN = {
method1:
'判定依據Adobe Source Han Sans與Source Han Serif;頁面則使用與它們同源的Noto Sans CJK與Noto Serif CJK顯示結果。每套字體讓中、港、台、日、韓五個地區共用一個字形池,並分別提供「Unicode碼點→字形編號(CID)」對應。兩地對應到同一CID,就視為同形。',
method2:
- '最終判定取黑體與宋體結果的並集:只要其中一款把兩地畫成同一字形,本站就按同形處理。這能排除只出現在單款字體中的設計細節。例如Source Han Sans為約五分之一的日本常用漢字提供獨立字形,其中約兩百個在Source Han Serif並未區分,像了、人、子、水、金;本站不把這類差異算作地區規範差異。',
+ '最終判定取黑體與宋體結果的並集:只要其中一款把兩地畫成同一字形,本應用就按同形處理。這能排除只出現在單款字體中的設計細節。例如Source Han Sans為約五分之一的日本常用漢字提供獨立字形,其中約兩百個在Source Han Serif並未區分,像了、人、子、水、金;本應用不把這類差異算作地區規範差異。',
method3:
'頁面會按這份判定重新分組:被判為同形的格子統一借用組內一個地區的Noto字體,因此螢幕上也會真正呈現同一輪廓。相應地,某個地區版本獨有、但被上述規則過濾的細小差異不會顯示。',
scope1:
@@ -166,7 +166,7 @@ export const zhHK: typeof zhCN = {
scope3:
'台灣《次常用國字表》只為已有行提供二級收錄狀態和候選,不參與生成新行;其6,343個主條目中有3,599個獨有條目明確在產品範圍之外。',
limitPrint:
- '本站只比較通用黑體與宋體中的印刷字形,不涵蓋手寫習慣,也不以教科書體的示範字形為準。日語教科書體主要為日語教學設計,並沒有與中、港、台、韓共用同一字形池的正式地區版本;若拼接風格相近但來源不同的字體,地區差異與字體自身的設計差異就無法分開。為了控制變數,本站只能選用同時提供五地版本的同源字體系列。',
+ '本應用只比較通用黑體與宋體中的印刷字形,不涵蓋手寫習慣,也不以教科書體的示範字形為準。日語教科書體主要為日語教學設計,並沒有與中、港、台、韓共用同一字形池的正式地區版本;若拼接風格相近但來源不同的字體,地區差異與字體自身的設計差異就無法分開。為了控制變數,本應用只能選用同時提供五地版本的同源字體系列。',
limit1:
'這裏測的是Source Han系列的地區字形設計,不是各地標準本身。它是個高質素的代理,因為Adobe的地區字形分別依據大陸《印刷通用漢字字形表》、台灣教育部《國字標準字體》、香港教育局《常用字字形表》、日本JIS X 0208/0213(JIS2004字形)與韓國KS X 1001/1002。',
limit2:
@@ -180,13 +180,13 @@ export const zhHK: typeof zhCN = {
limit6:
'每個地區格會區分字表主條目、括注異體和未收錄回退。未收錄格仍顯示傳承參考字形;這些狀態只在詳情頁說明,不在列表頁增加角標。',
notice1:
- '本站是根據公開數據製作的字形對照工具,不是各地的規範、詞典或教材。頁面顯示的是本站採用的數據與自動規則所得結果,不能據此斷定某個字在當地只有這一種「正確」形式。',
+ '本應用是根據公開數據製作的字形對照工具,不是各地的規範、詞典或教材。頁面顯示的是本應用採用的數據與自動規則所得結果,不能據此斷定某個字在當地只有這一種「正確」形式。',
notice2:
- '各地規範的適用範圍和定義不盡相同,字體也只是規範的一種設計實現。本站會整理、轉換及合併不同來源的數據,也會為未收錄項目補上參考字形;這些都是工程取捨,難免帶來簡化、遺漏和錯誤。',
+ '各地規範的適用範圍和定義不盡相同,字體也只是規範的一種設計實現。本應用會整理、轉換及合併不同來源的數據,也會為未收錄項目補上參考字形;這些都是工程取捨,難免帶來簡化、遺漏和錯誤。',
notice3:
'頁面中的「同形」或「不同」只在上述數據、字體與規則內成立;地區的排列和分組只為方便對照,不表示優劣或立場。正式場合請以原始規範與詞典為準;每個字的詳情頁都列有相應地區的字典連結。',
notice4: '漢字數量龐大,出錯難免。發現數據有誤,請透過{issues}回報。',
- data1: '本站的字表數據以JSON提供,可直接取用:',
+ data1: '本應用的字表數據以JSON提供,可直接取用:',
thanks:
'感謝OpenCC、Unicode Unihan、zispace/hanzi-chars、Jun Da、粵典、國家教育研究院、scriptin/kanji-frequency、Adobe Source Han與Noto CJK的維護者。逐字對照工具{tofu}是本項目的先行者,同樣用Noto系列區分地區字形。',
},
diff --git a/app/locales/zh-tw.ts b/app/locales/zh-tw.ts
index 0900799..b217975 100644
--- a/app/locales/zh-tw.ts
+++ b/app/locales/zh-tw.ts
@@ -156,7 +156,7 @@ export const zhTW: typeof zhCN = {
method1:
'判定依據Adobe Source Han Sans與Source Han Serif;頁面則使用與它們同源的Noto Sans CJK與Noto Serif CJK呈現結果。每套字體讓中、港、臺、日、韓五個地區共用一個字形池,並分別提供「Unicode碼位→字形編號(CID)」對映。兩地對映到同一CID,就視為同形。',
method2:
- '最終判定取黑體與宋體結果的聯集:只要其中一款把兩地畫成同一字形,本站就按同形處理。這能排除只出現在單款字體中的設計細節。例如Source Han Sans為約五分之一的日本常用漢字提供獨立字形,其中約兩百個在Source Han Serif並未區分,像了、人、子、水、金;本站不把這類差異算作地區規範差異。',
+ '最終判定取黑體與宋體結果的聯集:只要其中一款把兩地畫成同一字形,本應用就按同形處理。這能排除只出現在單款字體中的設計細節。例如Source Han Sans為約五分之一的日本常用漢字提供獨立字形,其中約兩百個在Source Han Serif並未區分,像了、人、子、水、金;本應用不把這類差異算作地區規範差異。',
method3:
'頁面會按這份判定重新分組:被判為同形的格子統一借用組內一個地區的Noto字體,因此螢幕上也會真正呈現同一輪廓。相應地,某個地區版本獨有、但被上述規則過濾的細小差異不會顯示。',
scope1:
@@ -166,7 +166,7 @@ export const zhTW: typeof zhCN = {
scope3:
'臺灣《次常用國字表》只為已有列提供二級收錄狀態和候選,不參與生成新列;其6,343個主條目中有3,599個獨有條目明確在產品範圍之外。',
limitPrint:
- '本站只比較通用黑體與宋體中的印刷字形,不涵蓋手寫習慣,也不以教科書體的示範字形為準。日語教科書體主要為日語教學設計,並沒有與中、港、臺、韓共用同一字形池的正式地區版本;若拼接風格相近但來源不同的字體,地區差異與字體自身的設計差異就無法分開。為了控制變因,本站只能選用同時提供五地版本的同源字體系列。',
+ '本應用只比較通用黑體與宋體中的印刷字形,不涵蓋手寫習慣,也不以教科書體的示範字形為準。日語教科書體主要為日語教學設計,並沒有與中、港、臺、韓共用同一字形池的正式地區版本;若拼接風格相近但來源不同的字體,地區差異與字體自身的設計差異就無法分開。為了控制變因,本應用只能選用同時提供五地版本的同源字體系列。',
limit1:
'這裡測的是Source Han系列的地區字形設計,不是各地標準本身。它是個高品質的代理,因為Adobe的地區字形分別依據大陸《印刷通用漢字字形表》、臺灣教育部《國字標準字體》、香港教育局《常用字字形表》、日本JIS X 0208/0213(JIS2004字形)與韓國KS X 1001/1002。',
limit2:
@@ -180,13 +180,13 @@ export const zhTW: typeof zhCN = {
limit6:
'每個地區格會區分字表主條目、括注異體和未收錄回退。未收錄格仍顯示傳承參考字形;這些狀態只在詳情頁說明,不在列表頁增加角標。',
notice1:
- '本站是依據公開資料製作的字形對照工具,不是各地的規範、辭典或教學材料。頁面呈現的是本站採用的資料與自動規則所得結果,不能據此斷定某個字在當地只有這一種「正確」形式。',
+ '本應用是依據公開資料製作的字形對照工具,不是各地的規範、辭典或教學材料。頁面呈現的是本應用採用的資料與自動規則所得結果,不能據此斷定某個字在當地只有這一種「正確」形式。',
notice2:
- '各地規範的適用範圍與定義不盡相同,字體也只是規範的一種設計實作。本站會整理、轉換並合併不同來源的資料,也會為未收錄項目補上參考字形;這些都是工程取捨,難免帶來簡化、遺漏與錯誤。',
+ '各地規範的適用範圍與定義不盡相同,字體也只是規範的一種設計實作。本應用會整理、轉換並合併不同來源的資料,也會為未收錄項目補上參考字形;這些都是工程取捨,難免帶來簡化、遺漏與錯誤。',
notice3:
'頁面中的「同形」或「不同」只在上述資料、字體與規則內成立;地區的排列與分組只為方便對照,不表示優劣或立場。正式場合請以原始規範與辭典為準;每個字的詳情頁都列有相應地區的字典連結。',
notice4: '漢字數量龐大,出錯難免。發現資料有誤,請透過{issues}回報。',
- data1: '本站的字表資料以JSON提供,可直接取用:',
+ data1: '本應用的字表資料以JSON提供,可直接取用:',
thanks:
'感謝OpenCC、Unicode Unihan、zispace/hanzi-chars、Jun Da、粵典、國家教育研究院、scriptin/kanji-frequency、Adobe Source Han與Noto CJK的維護者。逐字對照工具{tofu}是本專案的先行者,同樣用Noto系列區分地區字形。',
},
diff --git a/app/pages/about.vue b/app/pages/about.vue
index 3ddb19b..0ca7ce5 100644
--- a/app/pages/about.vue
+++ b/app/pages/about.vue
@@ -1,5 +1,6 @@