diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9e14e8e..283966d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -27,10 +27,13 @@ jobs: uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: data/raw + # The build prunes retired files restored through this fallback key. key: sources-v1-${{ runner.os }}-${{ hashFiles('data/sources.lock.json') }} restore-keys: sources-v1-${{ runner.os }}- - name: Build dataset + # Deliberately rebuilt instead of restored: raw inputs are cached, and + # generated JSON/stroke assets must never survive a schema change. run: pnpm build:dataset - name: Restore generated fonts @@ -40,6 +43,8 @@ jobs: path: | public/fonts/*.woff2 public/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') }} - name: Build fonts diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..aeac28b --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,34 @@ +name: Lint + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: {} + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Setup JS + uses: sxzz/workflows/setup-js@main + + - name: Restore source downloads + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: data/raw + # Keep this aligned with deploy.yml so both workflows share downloads. + key: sources-v1-${{ runner.os }}-${{ hashFiles('data/sources.lock.json') }} + restore-keys: sources-v1-${{ runner.os }}- + + - name: Build dataset + run: pnpm build:dataset + + - name: Type Check + run: pnpm typecheck + + - name: Lint + run: pnpm lint diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml deleted file mode 100644 index f1454b4..0000000 --- a/.github/workflows/unit-test.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Unit Test - -on: - push: - branches: [main] - pull_request: - branches: [main] - -permissions: {} - -jobs: - unit-test: - uses: sxzz/workflows/.github/workflows/unit-test.yml@main - with: - skip-test: true diff --git a/.gitignore b/.gitignore index 6277157..6b2e376 100644 --- a/.gitignore +++ b/.gitignore @@ -12,11 +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:data: 12MB of font binaries and the @font-face rules -# that point at them. The character table itself (public/data) is committed. +# that point at them. public/fonts/*.woff2 public/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/ + # Local record of the development conversation: 11MB of JSONL plus screenshots. # Kept on disk for reference, out of the repository by default. docs/session/ diff --git a/.prettierignore b/.prettierignore index 4ed3f5a..f4c12dc 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,7 +1,7 @@ -# Generated by scripts/build-data.ts: 8,446 rows on one line each, and -# reformatting it would only make the diffs unreadable. +# Generated by scripts/build-data.ts and not committed. public/data/chars.json public/data/sources.json +public/strokes/ # Generated by scripts/build-fonts.ts alongside the woff2 files public/fonts/fonts-*.css diff --git a/README.ja-JP.md b/README.ja-JP.md index 2b716e4..1085beb 100644 --- a/README.ja-JP.md +++ b/README.ja-JP.md @@ -63,7 +63,7 @@ ```bash pnpm install -pnpm build:data # 字表とフォントサブセットを生成。初回は約215MBをダウンロードし、以後はキャッシュを使用 +pnpm build:data # 字表とフォントサブセットを生成。初回は約261 MiBをダウンロードし、以後はキャッシュを使用 pnpm update:sources # サードパーティデータの更新を確認して固定。変更があればダウンロードして再生成 pnpm dev pnpm test @@ -72,7 +72,7 @@ pnpm generate # 静的サイト 各行のURLには行名を使います(`/char/着`)。5地域の表示字形、`aka`、`alternatives` もURLとして利用でき、クライアント側で対応する行へ移動します。たとえば `/char/国`、`/char/郞`、`/char/缐` です。ページの `rel=canonical` は行名のURLを指します。未確認関係はURL別名にはなりません。 -字表 `public/data/chars.json` はリポジトリにコミットされ、サイト上でも `/data/chars.json` から公開データとして取得できます。すべての `/data/*.json` レスポンスは任意のオリジンからのクロスオリジン読み取りを許可します。フォントサブセットは約12MBで、リポジトリには**コミットしません**。`pnpm build:data` で生成するため、ビルド前に一度実行する必要があります。元データのダウンロードは `data/raw/` 以下に種類別(`charlist/`、`opencc/`、`cmap/`、`font/`、`unihan/`、`frequency/`)でキャッシュされ、gitignoreされています。 +字表 `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されています。ビルド時には、古いキャッシュから復元されたものの、現在の出典一覧には存在しないファイルを削除します。 ## デプロイ @@ -94,7 +94,9 @@ Cloudflareの **Settings → Domains & Routes** でproductionドメインを接 各字群の詳細ページはそれぞれ独立したHTMLとして生成されます。ページデータはローカルbundleに含まれるため、ルートごとの追加 `_payload.json` を生成するpayload extractionは無効にしています。地域異体字の別名については、リダイレクト専用ページを生成しません。Static Assetsがまず `404.html` とHTTP 404を返し、その後Nuxtのクライアントミドルウェアが対応する行へ移動します。これにより、検索エンジンが別名を成功ページとして重複登録することを避けます。実際に存在しないURLはHTTP 404のままです。`public/_headers` では、内容ハッシュ付きの `_nuxt/*` に長期キャッシュを設定します。 -サードパーティ資産の具体的なcommit、公式添付ファイル識別子、SHA-256は `data/sources.lock.json` に記録しています。更新時には `pnpm update:sources` を実行します。バージョンのある上流データについてはバージョン番号を解決し、バージョンのない公式直リンクについては改めて検証します。内容が変わっていればlockfileを更新してデータを再生成し、まったく変わっていなければ生成をスキップします。ビルド時の `pnpm build:data` はlockfileに従い、約 **215 MB** の元データをダウンロードして検証します。このうち195MBは10個のNoto CJKフォントです。明示的な更新を行っていない直リンクの内容が変わった場合は、チェックサム不一致として失敗し、黙ってデータに取り込むことはありません。Actionsでは元データのダウンロードと生成フォントを別々にキャッシュします。前者はlockfileだけで決まり、後者はlockfile、実際の生成スクリプト、関連依存関係、locale、字表から決まります。フォント入力が完全に同じ場合は、データ生成を省略します。 +サードパーティ資産の具体的な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つの選択肢にまとめます。筆順シャードは安定したパスを使用し、専用のキャッシュポリシーは設定しません。 ローカルでも、ビルド後に直接アップロードできます。 @@ -113,7 +115,7 @@ pnpm deploy | 5地域の字形差異の判定 | [Adobe Source Han Sans / Serif(CMapリソース)](https://github.com/adobe-fonts/source-han-sans) | [SIL OFL 1.1](https://openfontlicense.org/) | | ページ表示用フォント | [Noto Sans / Noto Serif(CJKを含む)](https://github.com/notofonts/noto-cjk) | [SIL OFL 1.1](https://openfontlicense.org/) | | 簡体・繁体、香港・台湾異体字、日本新旧字体の対応 | [OpenCC(中国語文字変換)](https://github.com/BYVoid/OpenCC) | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | -| 日本字形の筆順アニメーション | [KanjiVG](https://kanjivg.tagaini.net/) | [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) | +| 中国・台湾・日本・韓国の筆順アニメーション | [AnimCJK](https://github.com/parsimonhi/animCJK) | [Arphic Public License](https://github.com/parsimonhi/animCJK/blob/master/licenses/APL/english/ARPHICPL.TXT) | | 5地域の標準字表 | [zispace/hanzi-chars](https://github.com/zispace/hanzi-chars) | [リポジトリに記載なし](https://github.com/zispace/hanzi-chars) | | 画数・読み | [Unicode Han Database(Unihan)](https://www.unicode.org/reports/tr38/) | [Unicode License v3](https://www.unicode.org/license.txt) | | 韓国式異体字の対応 | [Unicode IRG N2200(韓国教育用漢字提案)](https://www.unicode.org/L2/L2017/17173-irgn2200-unihan-db.pdf) | [Unicode License v3](https://www.unicode.org/license.txt) | diff --git a/README.md b/README.md index 5149549..a6511a3 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ ```bash pnpm install -pnpm build:data # 生成字表与字体子集,首次会下载约 215MB 原始数据,之后走缓存 +pnpm build:data # 生成字表与字体子集,首次会下载约 261 MiB 原始数据,之后走缓存 pnpm update:sources # 检查并锁定新版第三方数据;有变化时下载并重新生成 pnpm dev pnpm test @@ -72,7 +72,7 @@ pnpm generate # 静态站点 每一行的地址是它的行名(`/char/着`)。五地展示形、`aka` 和 `alternatives` 也可作为地址,由客户端跳到所属的行——例如 `/char/国`、`/char/郞`、`/char/缐`,页面用 `rel=canonical` 指回行名地址;未确认关系不是地址别名。 -字表 `public/data/chars.json` 提交在仓库里,也是站点的开放数据地址 `/data/chars.json`;所有 `/data/*.json` 响应均允许任意来源跨域读取。字体子集约 12MB,**不提交**,由 `pnpm build:data` 生成——所以构建前必须先跑一次。原始下载缓存在 `data/raw/` 下按类别存放(`charlist/`、`opencc/`、`cmap/`、`font/`、`unihan/`、`frequency/`),已 gitignore。 +字表 `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;构建会清理从旧缓存恢复、但已不在当前来源清单中的文件。 ## 部署 @@ -94,7 +94,9 @@ Cloudflare Worker 名称须为 `hanji`,与 `wrangler.json` 中的 `name` 一 每个字组详情页都会生成独立 HTML;页面数据在本地 bundle 中,因此关闭了每路由额外生成 `_payload.json` 的 payload extraction。地区异体别名不另外生成跳转页:它先由 Static Assets 返回 `404.html` 和 HTTP 404,再由 Nuxt 客户端中间件跳到所属行;搜索引擎不会把 alias 当作成功页面重复收录。真正未知的地址保持 HTTP 404;`public/_headers` 给带内容哈希的 `_nuxt/*` 设了长缓存。 -第三方资产的具体 commit、官方附件标识与 SHA-256 记录在 `data/sources.lock.json`;需要升级时运行 `pnpm update:sources`。它会解析有版本上游的版本号,并重新校验没有版本号的官方直链;内容有变化时更新 lockfile 并直接重新生成数据,完全未变则跳过生成。构建时 `pnpm build:data` 会按 lockfile 下载并校验约 **215 MB** 原始数据(其中 195 MB 是十份 Noto CJK 字体);任何未显式更新的直链内容变化都会因校验和不符而失败,不会静默进入数据。Actions 分开缓存原始下载与生成字体:前者只由 lockfile 决定,后者由 lockfile、实际生成脚本、相关依赖、locale 与字表决定;字体输入完全不变时跳过数据生成。 +第三方资产的具体 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/`,不提交到仓库;部署流程会在测试和静态生成前重建它们。同一字组内,按笔画顺序排列的轮廓完全一致时只保存第一份变体及其中线,界面也把对应地区合并为一个选择项。笔顺分片使用稳定路径,不设置专用缓存策略。 本地也可构建后直传: @@ -113,7 +115,7 @@ pnpm deploy | 判定五地字形差异 | [Adobe Source Han Sans / Serif(CMap 资源)](https://github.com/adobe-fonts/source-han-sans) | [SIL OFL 1.1](https://openfontlicense.org/) | | 页面展示用字体 | [Noto Sans / Noto Serif(含 CJK)](https://github.com/notofonts/noto-cjk) | [SIL OFL 1.1](https://openfontlicense.org/) | | 简繁、港台异体、日本新旧字体对应 | [OpenCC 开放中文转换](https://github.com/BYVoid/OpenCC) | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | -| 日本字形笔顺动画 | [KanjiVG](https://kanjivg.tagaini.net/) | [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) | +| 中、台、日、韩笔顺动画 | [AnimCJK](https://github.com/parsimonhi/animCJK) | [Arphic Public License](https://github.com/parsimonhi/animCJK/blob/master/licenses/APL/english/ARPHICPL.TXT) | | 五地标准字表 | [zispace/hanzi-chars](https://github.com/zispace/hanzi-chars) | [仓库未声明](https://github.com/zispace/hanzi-chars) | | 笔画数、读音 | [Unicode Han Database (Unihan)](https://www.unicode.org/reports/tr38/) | [Unicode License v3](https://www.unicode.org/license.txt) | | 韩式异体对应 | [Unicode IRG N2200(韩国教育用汉字提案)](https://www.unicode.org/L2/L2017/17173-irgn2200-unihan-db.pdf) | [Unicode License v3](https://www.unicode.org/license.txt) | diff --git a/app/components/DataSources.vue b/app/components/DataSources.vue index bd7f441..baab0dd 100644 --- a/app/components/DataSources.vue +++ b/app/components/DataSources.vue @@ -14,7 +14,15 @@ const localize = (