diff --git a/package.json b/package.json index 5726219..59b10b2 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "packageManager": "npm@11.7.0", "scripts": { "ng": "ng", - "docs:generate": "tsx scripts/generate-docs.ts", + "docs:generate": "npm run articles:generate && npm run docs:generate:content", + "docs:generate:content": "tsx scripts/generate-docs.ts", "articles:generate": "tsx scripts/generate-articles.ts", "articles:inspect-note": "tsx scripts/inspect-note-article.ts", "articles:stage-zenn": "tsx scripts/import-zenn-articles.ts", @@ -26,7 +27,7 @@ "prebuild:web-site": "npm run articles:generate", "build:web-site": "ng build web-site && pagefind --site dist/web-site/browser --output-subdir pagefind && tsx --test scripts/web-site-build-output.test.ts", "watch": "ng build docs --watch --configuration development", - "pretest": "npm run docs:generate && npm run articles:generate", + "pretest": "npm run docs:generate", "test": "tsx --test scripts/docgen-api.test.ts scripts/docgen-inline-code.test.ts scripts/docgen-readme.test.ts scripts/html-policy.test.ts scripts/markdown-headings.test.ts scripts/package-markdown.test.ts scripts/package-repository.test.ts scripts/github-sponsors.test.ts scripts/bilingual-update-blocker.test.ts scripts/site-contract.test.ts scripts/zenn-articles.test.ts scripts/note-articles.test.ts scripts/import-zenn-articles.test.ts scripts/generate-articles.test.ts scripts/article-relations.test.ts scripts/seo-dates.test.ts shared/json-ld.test.ts scripts/seo-audit.test.ts && ng test docs --watch=false && ng test web-site --watch=false", "fmt": "prettier --write .", "fmt:check": "prettier --check .", diff --git a/projects/docs/public/_redirects b/projects/docs/public/_redirects index 5f41bce..a610461 100644 --- a/projects/docs/public/_redirects +++ b/projects/docs/public/_redirects @@ -1,3 +1,6 @@ +# Historical source URL discovered before ESLint rule pages had canonical docs routes. +/src/rules/ionic-attr-type-check.ts https://docs.rdlabo.dev/projects/eslint-plugin-rules/docs/rules/ionic-attr-type-check 301 + # Legacy Stripe documentation routes. stripe.capacitorjs.jp preserves the # request path when forwarding to docs.rdlabo.dev, so resolve it at the edge # before Angular's compatibility routes render a client-side redirect page. diff --git a/projects/docs/public/sitemap.xml b/projects/docs/public/sitemap.xml index b69e5bc..9faf430 100644 --- a/projects/docs/public/sitemap.xml +++ b/projects/docs/public/sitemap.xml @@ -122,9 +122,11 @@ https://docs.rdlabo.dev/projects/ionic-angular-scroll-header/docs/safe-area + 2026-08-31 https://docs.rdlabo.dev/ja/projects/ionic-angular-scroll-header/docs/safe-area + 2026-08-31 https://docs.rdlabo.dev/projects/ionic-angular-scroll-header/docs/api @@ -378,9 +380,11 @@ https://docs.rdlabo.dev/projects/eslint-plugin-rules/docs/configuration + 2026-08-31 https://docs.rdlabo.dev/ja/projects/eslint-plugin-rules/docs/configuration + 2026-08-31 https://docs.rdlabo.dev/projects/eslint-plugin-rules/docs/migration @@ -390,9 +394,11 @@ https://docs.rdlabo.dev/projects/eslint-plugin-rules/docs/rules + 2026-08-31 https://docs.rdlabo.dev/ja/projects/eslint-plugin-rules/docs/rules + 2026-08-31 https://docs.rdlabo.dev/projects/eslint-plugin-rules/docs/api @@ -546,9 +552,11 @@ https://docs.rdlabo.dev/projects/capacitor-stripe/docs/configuration + 2026-08-31 https://docs.rdlabo.dev/ja/projects/capacitor-stripe/docs/configuration + 2026-08-31 https://docs.rdlabo.dev/projects/capacitor-stripe/docs/vanilla-js @@ -600,15 +608,19 @@ https://docs.rdlabo.dev/projects/capacitor-stripe/docs/apple-pay + 2026-08-31 https://docs.rdlabo.dev/ja/projects/capacitor-stripe/docs/apple-pay + 2026-08-31 https://docs.rdlabo.dev/projects/capacitor-stripe/docs/google-pay + 2026-08-31 https://docs.rdlabo.dev/ja/projects/capacitor-stripe/docs/google-pay + 2026-08-31 https://docs.rdlabo.dev/projects/capacitor-stripe/docs/api @@ -684,9 +696,11 @@ https://docs.rdlabo.dev/projects/capacitor-admob/docs/readme + 2026-08-31 https://docs.rdlabo.dev/ja/projects/capacitor-admob/docs/readme + 2026-08-31 https://docs.rdlabo.dev/projects/capacitor-admob/docs/configuration @@ -708,9 +722,11 @@ https://docs.rdlabo.dev/projects/capacitor-admob/docs/interstitial + 2026-08-31 https://docs.rdlabo.dev/ja/projects/capacitor-admob/docs/interstitial + 2026-08-31 https://docs.rdlabo.dev/projects/capacitor-admob/docs/rewarded diff --git a/projects/docs/src/app/docs/docs-data.ts b/projects/docs/src/app/docs/docs-data.ts index 8595e1d..3aa01ea 100644 --- a/projects/docs/src/app/docs/docs-data.ts +++ b/projects/docs/src/app/docs/docs-data.ts @@ -63,6 +63,7 @@ export interface DocsPageSummary { title: string; navTitle: string; seoTitle?: string; + seoDescription?: string; updatedAt?: string; slug: string; section: string; diff --git a/projects/docs/src/app/docs/docs-page.ts b/projects/docs/src/app/docs/docs-page.ts index 25a34da..919662d 100644 --- a/projects/docs/src/app/docs/docs-page.ts +++ b/projects/docs/src/app/docs/docs-page.ts @@ -231,7 +231,7 @@ export class DocsPageComponent implements OnInit, AfterViewInit { this.#seo.setPage({ title: page.seoTitle ?? `${page.title} - ${project.shortName} - rdlabo.dev`, - description: `${page.title}. ${project.description}`, + description: page.seoDescription ?? `${page.title}. ${project.description}`, path: page.path, structuredData: docsBreadcrumbStructuredData(this.#locale, [ { name: 'rdlabo.dev', path: '/' }, diff --git a/projects/docs/src/app/docs/landing-page.spec.ts b/projects/docs/src/app/docs/landing-page.spec.ts index debbc8f..ccbf0a7 100644 --- a/projects/docs/src/app/docs/landing-page.spec.ts +++ b/projects/docs/src/app/docs/landing-page.spec.ts @@ -27,7 +27,9 @@ describe('LandingPageComponent', () => { it('renders a manifest-driven Stripe landing page', async () => { const compiled = await setup('stripe'); - expect(TestBed.inject(Title).getTitle()).toBe('Stripe - rdlabo.dev'); + expect(TestBed.inject(Title).getTitle()).toBe( + 'Capacitor Stripe Plugin Documentation | rdlabo', + ); expect(compiled.querySelector('h1')?.textContent).toContain( 'Accept Stripe payments in Capacitor apps', ); diff --git a/projects/docs/src/app/generated/project-catalog.generated.ts b/projects/docs/src/app/generated/project-catalog.generated.ts index ad00a3d..85ca629 100644 --- a/projects/docs/src/app/generated/project-catalog.generated.ts +++ b/projects/docs/src/app/generated/project-catalog.generated.ts @@ -390,8 +390,6 @@ export const PROJECTS_EN = [ { "title": "Getting Started", "navTitle": "Getting Started", - "seoTitle": "Angular CDK Variable Height Virtual Scroll Setup | rdlabo", - "updatedAt": "2026-08-25", "slug": "readme", "section": "Quickstart", "path": "/projects/ngx-cdk-scroll-strategies/docs/readme" @@ -399,8 +397,6 @@ export const PROJECTS_EN = [ { "title": "Simple Usage", "navTitle": "Simple Usage", - "seoTitle": "Angular CDK Virtual Scroll: Variable Height Example | rdlabo", - "updatedAt": "2026-08-25", "demo": { "url": "https://rdlabo-ionic-angular-library.netlify.app/main/scroll-strategies/simple", "title": "Interactive simple virtual scroll demo" @@ -412,8 +408,6 @@ export const PROJECTS_EN = [ { "title": "Advanced Usage", "navTitle": "Advanced Usage", - "seoTitle": "Angular CDK Virtual Scroll: Dynamic Item Height | rdlabo", - "updatedAt": "2026-08-25", "demo": { "url": "https://rdlabo-ionic-angular-library.netlify.app/main/scroll-strategies/advanced", "title": "Interactive advanced virtual scroll demo" @@ -425,8 +419,6 @@ export const PROJECTS_EN = [ { "title": "Reverse Scroll", "navTitle": "Reverse Scroll", - "seoTitle": "Angular CDK Reverse Virtual Scroll for Chat UIs | rdlabo", - "updatedAt": "2026-08-25", "demo": { "url": "https://rdlabo-ionic-angular-library.netlify.app/main/scroll-strategies/reverse", "title": "Interactive reverse virtual scroll demo" @@ -438,8 +430,6 @@ export const PROJECTS_EN = [ { "title": "FAQ", "navTitle": "FAQ", - "seoTitle": "Angular CDK Virtual Scroll: Autosize vs Variable Height", - "updatedAt": "2026-08-25", "slug": "faq", "section": "Guides", "path": "/projects/ngx-cdk-scroll-strategies/docs/faq" @@ -1034,7 +1024,8 @@ export const PROJECTS_EN = [ "category": "capacitor-plugins", "icon": "payments", "version": "8.2.1", - "description": "Native Stripe payments for Capacitor applications.", + "seoTitle": "Capacitor Stripe Plugin Documentation | rdlabo", + "description": "Integrate Stripe PaymentSheet, Apple Pay, and Google Pay in Capacitor apps with @capacitor-community/stripe for iOS, Android, and web.", "headline": "Accept Stripe payments in Capacitor apps", "overview": "Present native PaymentSheet and PaymentFlow, accept Apple Pay and Google Pay, and integrate payments on the web from the same Capacitor codebase.", "featuresHeading": "What you can build", @@ -1114,7 +1105,6 @@ export const PROJECTS_EN = [ { "title": "PaymentSheet", "navTitle": "PaymentSheet", - "seoTitle": "Capacitor Stripe PaymentSheet API | rdlabo", "slug": "payment-sheet", "section": "Methods", "path": "/projects/capacitor-stripe/docs/payment-sheet" @@ -2037,8 +2027,6 @@ export const PROJECTS_JA = [ { "title": "はじめに", "navTitle": "はじめに", - "seoTitle": "Angular CDK可変高さVirtual Scrollの導入方法 | rdlabo", - "updatedAt": "2026-08-25", "slug": "readme", "section": "クイックスタート", "path": "/projects/ngx-cdk-scroll-strategies/docs/readme" @@ -2046,8 +2034,6 @@ export const PROJECTS_JA = [ { "title": "シンプルな使い方", "navTitle": "シンプルな使い方", - "seoTitle": "Angular CDK Virtual Scrollの可変高さサンプル | rdlabo", - "updatedAt": "2026-08-25", "demo": { "url": "https://rdlabo-ionic-angular-library.netlify.app/main/scroll-strategies/simple", "title": "Simple Virtual Scrollの操作デモ" @@ -2059,8 +2045,6 @@ export const PROJECTS_JA = [ { "title": "応用的な使い方", "navTitle": "応用的な使い方", - "seoTitle": "Angular CDK Virtual Scrollで動的なItem Heightを計測 | rdlabo", - "updatedAt": "2026-08-25", "demo": { "url": "https://rdlabo-ionic-angular-library.netlify.app/main/scroll-strategies/advanced", "title": "Advanced Virtual Scrollの操作デモ" @@ -2072,8 +2056,6 @@ export const PROJECTS_JA = [ { "title": "リバーススクロール", "navTitle": "リバーススクロール", - "seoTitle": "Angular CDK Reverse Virtual ScrollでChat UIを実装 | rdlabo", - "updatedAt": "2026-08-25", "demo": { "url": "https://rdlabo-ionic-angular-library.netlify.app/main/scroll-strategies/reverse", "title": "Reverse Virtual Scrollの操作デモ" @@ -2085,8 +2067,6 @@ export const PROJECTS_JA = [ { "title": "FAQ", "navTitle": "FAQ", - "seoTitle": "Angular CDK Virtual Scrollのautosizeと可変高さの違い", - "updatedAt": "2026-08-25", "slug": "faq", "section": "ガイド", "path": "/projects/ngx-cdk-scroll-strategies/docs/faq" @@ -2681,7 +2661,8 @@ export const PROJECTS_JA = [ "category": "capacitor-plugins", "icon": "payments", "version": "8.2.1", - "description": "Capacitor アプリで Stripe のネイティブ決済を利用するためのプラグイン。", + "seoTitle": "Capacitor Stripe プラグイン ドキュメント | rdlabo", + "description": "Capacitorアプリに@capacitor-community/stripeを導入し、iOS・Android・WebでPaymentSheet、Apple Pay、Google Payを実装するためのドキュメント。", "headline": "CapacitorアプリでStripe決済を受け付ける", "overview": "同じCapacitorコードベースからネイティブのPaymentSheetとPaymentFlow、Apple Pay、Google Pay、Web決済を利用できます。", "featuresHeading": "実装できること", @@ -2761,7 +2742,6 @@ export const PROJECTS_JA = [ { "title": "PaymentSheet", "navTitle": "PaymentSheet", - "seoTitle": "Capacitor Stripe PaymentSheet API リファレンス | rdlabo", "slug": "payment-sheet", "section": "メソッド", "path": "/projects/capacitor-stripe/docs/payment-sheet" diff --git a/projects/docs/src/app/generated/projects/admob.en.generated.ts b/projects/docs/src/app/generated/projects/admob.en.generated.ts index b34526a..cec042c 100644 --- a/projects/docs/src/app/generated/projects/admob.en.generated.ts +++ b/projects/docs/src/app/generated/projects/admob.en.generated.ts @@ -37,6 +37,9 @@ export const PROJECT = { { "title": "Getting Started", "navTitle": "Getting Started", + "seoTitle": "Capacitor AdMob Plugin Setup Guide | rdlabo", + "seoDescription": "Install and configure @capacitor-community/admob to initialize Google Mobile Ads and display native ads in Capacitor apps on iOS and Android.", + "updatedAt": "2026-08-31", "slug": "readme", "file": "readme.md", "section": "Quickstart", @@ -177,6 +180,9 @@ export const PROJECT = { { "title": "Interstitial Ads", "navTitle": "Interstitial Ads", + "seoTitle": "Capacitor AdMob Interstitial Ads Guide | rdlabo", + "seoDescription": "Prepare, show, and handle interstitial ad events in Capacitor apps with @capacitor-community/admob on iOS and Android.", + "updatedAt": "2026-08-31", "slug": "interstitial", "file": "interstitial.md", "section": "Ad formats", diff --git a/projects/docs/src/app/generated/projects/admob.ja.generated.ts b/projects/docs/src/app/generated/projects/admob.ja.generated.ts index 628733b..de97421 100644 --- a/projects/docs/src/app/generated/projects/admob.ja.generated.ts +++ b/projects/docs/src/app/generated/projects/admob.ja.generated.ts @@ -37,6 +37,9 @@ export const PROJECT = { { "title": "はじめに", "navTitle": "はじめに", + "seoTitle": "Capacitor AdMob プラグイン導入ガイド | rdlabo", + "seoDescription": "@capacitor-community/admobを導入し、iOS・AndroidのCapacitorアプリでGoogle Mobile Adsを初期化してネイティブ広告を表示します。", + "updatedAt": "2026-08-31", "slug": "readme", "file": "readme.md", "section": "クイックスタート", @@ -273,6 +276,9 @@ export const PROJECT = { { "title": "インタースティシャル広告", "navTitle": "インタースティシャル広告", + "seoTitle": "Capacitor AdMobインタースティシャル広告ガイド | rdlabo", + "seoDescription": "@capacitor-community/admobを使い、iOS・AndroidのCapacitorアプリでインタースティシャル広告の準備・表示・Event処理を実装します。", + "updatedAt": "2026-08-31", "slug": "interstitial", "file": "interstitial.md", "section": "広告フォーマット", diff --git a/projects/docs/src/app/generated/projects/eslint-plugin-rules.en.generated.ts b/projects/docs/src/app/generated/projects/eslint-plugin-rules.en.generated.ts index 90de746..c820f6c 100644 --- a/projects/docs/src/app/generated/projects/eslint-plugin-rules.en.generated.ts +++ b/projects/docs/src/app/generated/projects/eslint-plugin-rules.en.generated.ts @@ -70,6 +70,9 @@ export const PROJECT = { { "title": "Configuration", "navTitle": "Configuration", + "seoTitle": "Configure Angular and Ionic ESLint Rules | rdlabo", + "seoDescription": "Configure @rdlabo/eslint-plugin-rules with flat config presets for Angular, Ionic, or framework-independent TypeScript projects.", + "updatedAt": "2026-08-31", "slug": "configuration", "file": "configuration.md", "section": "Guide", @@ -138,6 +141,9 @@ export const PROJECT = { { "title": "Rules", "navTitle": "Rules", + "seoTitle": "Angular, Ionic, and TypeScript ESLint Rules | rdlabo", + "seoDescription": "Browse every @rdlabo/eslint-plugin-rules rule for Angular Signals, Ionic components, component boundaries, forms, and safe asynchronous code.", + "updatedAt": "2026-08-31", "slug": "rules", "file": "rules.md", "section": "Reference", diff --git a/projects/docs/src/app/generated/projects/eslint-plugin-rules.ja.generated.ts b/projects/docs/src/app/generated/projects/eslint-plugin-rules.ja.generated.ts index bb65adb..a31fe2f 100644 --- a/projects/docs/src/app/generated/projects/eslint-plugin-rules.ja.generated.ts +++ b/projects/docs/src/app/generated/projects/eslint-plugin-rules.ja.generated.ts @@ -60,6 +60,9 @@ export const PROJECT = { { "title": "設定", "navTitle": "設定", + "seoTitle": "Angular・Ionic向けESLintルールの設定 | rdlabo", + "seoDescription": "@rdlabo/eslint-plugin-rulesのFlat Configを使い、Angular・Ionic・汎用TypeScript向けのESLintプリセットを設定します。", + "updatedAt": "2026-08-31", "slug": "configuration", "file": "configuration.md", "section": "ガイド", @@ -128,6 +131,9 @@ export const PROJECT = { { "title": "ルール一覧", "navTitle": "ルール一覧", + "seoTitle": "Angular・Ionic・TypeScript向けESLintルール一覧 | rdlabo", + "seoDescription": "Angular Signal、Ionic Component、Component境界、フォーム、安全な非同期処理を検査する@rdlabo/eslint-plugin-rulesのルール一覧です。", + "updatedAt": "2026-08-31", "slug": "rules", "file": "rules.md", "section": "リファレンス", diff --git a/projects/docs/src/app/generated/projects/ionic-angular-scroll-header.en.generated.ts b/projects/docs/src/app/generated/projects/ionic-angular-scroll-header.en.generated.ts index daaaa83..3bda22b 100644 --- a/projects/docs/src/app/generated/projects/ionic-angular-scroll-header.en.generated.ts +++ b/projects/docs/src/app/generated/projects/ionic-angular-scroll-header.en.generated.ts @@ -117,6 +117,9 @@ export const PROJECT = { { "title": "Safe Area", "navTitle": "Safe Area", + "seoTitle": "Ionic Safe Area with Scroll-Aware Headers | rdlabo", + "seoDescription": "Configure hidden safe-area headers and always-visible native headers with @rdlabo/ionic-angular-scroll-header for Ionic content layouts.", + "updatedAt": "2026-08-31", "slug": "safe-area", "file": "safe-area.md", "section": "Guides", diff --git a/projects/docs/src/app/generated/projects/ionic-angular-scroll-header.ja.generated.ts b/projects/docs/src/app/generated/projects/ionic-angular-scroll-header.ja.generated.ts index b2adcb1..8295887 100644 --- a/projects/docs/src/app/generated/projects/ionic-angular-scroll-header.ja.generated.ts +++ b/projects/docs/src/app/generated/projects/ionic-angular-scroll-header.ja.generated.ts @@ -85,7 +85,7 @@ export const PROJECT = { "file": "ion-content.md", "section": "ガイド", "path": "/projects/ionic-angular-scroll-header/docs/ion-content", - "html": "

Ionic Content に Scroll 連動 Header を付けます。インストール のあとで呼び出します。

\n\n
import { ScrollHeaderDirective } from '@rdlabo/ionic-angular-scroll-header';\n@Component({\n  ...\n  imports: [\n    ScrollHeaderDirective\n  ],\n})\n
<ion-header class=\"hidden\"><ion-toolbar></ion-toolbar></ion-header>\n<!-- set hidden header for safe-area -->\n<ion-content rdlaboScrollHeader>\n  <ion-header>\n    <ion-toolbar>...</ion-toolbar>\n    <!-- Default Header for display -->\n  </ion-header>\n  ...Your Content\n</ion-content>\n
", + "html": "

Ionic Content に Scroll 連動 Header を付けます。インストール のあとで呼び出します。Safe Area用の非表示Headerと常時表示するNative Headerの使い分けは、Safe Areaガイドを参照してください。

\n\n
import { ScrollHeaderDirective } from '@rdlabo/ionic-angular-scroll-header';\n@Component({\n  ...\n  imports: [\n    ScrollHeaderDirective\n  ],\n})\n
<ion-header class=\"hidden\"><ion-toolbar></ion-toolbar></ion-header>\n<!-- set hidden header for safe-area -->\n<ion-content rdlaboScrollHeader>\n  <ion-header>\n    <ion-toolbar>...</ion-toolbar>\n    <!-- Default Header for display -->\n  </ion-header>\n  ...Your Content\n</ion-content>\n
", "headings": [], "codes": [], "scrollMap": [], @@ -117,11 +117,14 @@ export const PROJECT = { { "title": "Safe Area", "navTitle": "Safe Area", + "seoTitle": "IonicのSafe AreaとScroll連動Header | rdlabo", + "seoDescription": "Ionic・Angular CDKのScroll連動Headerで、Safe Area用の非表示Headerと常時表示するNative Headerを設定する方法を解説します。", + "updatedAt": "2026-08-31", "slug": "safe-area", "file": "safe-area.md", "section": "ガイド", "path": "/projects/ionic-angular-scroll-header/docs/safe-area", - "html": "

Safe Area 用の非表示 Header と常時表示 Native Header です。

\n

セーフエリア用に hidden な Header を設定する必要があるのはなぜですか?

\n

もちろん、次のように ion-content にセーフエリアを設定することもできます。

\n
ion-content {\n  padding-top: var(--ion-safe-area-top, 0);\n}\n

ただし、セーフエリアのために ion-header と ion-toolbar を明示的に置く方が望ましいと考えました。

\n

Scroll に追従して隠れる Header とは別に、常に表示される Header も必要です

\n

可能です。クラス名に native-header を追加すると、2 つの Header をよりスムーズに扱えます。

\n
- <ion-header class=\"hidden\"><ion-toolbar></ion-toolbar></ion-header>\n+ <ion-header class=\"native-header\">\n+   <ion-toolbar><ion-title>Native Header</ion-title></ion-toolbar>\n+ </ion-header>\n
", + "html": "

Safe Area 用の非表示 Header と常時表示 Native Header です。

\n

まずIonContentガイドのScroll連動Headerを設定し、このページのHeader構成を用途に応じて選択してください。

\n

セーフエリア用に hidden な Header を設定する必要があるのはなぜですか?

\n

もちろん、次のように ion-content にセーフエリアを設定することもできます。

\n
ion-content {\n  padding-top: var(--ion-safe-area-top, 0);\n}\n

ただし、セーフエリアのために ion-header と ion-toolbar を明示的に置く方が望ましいと考えました。

\n

Scroll に追従して隠れる Header とは別に、常に表示される Header も必要です

\n

可能です。クラス名に native-header を追加すると、2 つの Header をよりスムーズに扱えます。

\n
- <ion-header class=\"hidden\"><ion-toolbar></ion-toolbar></ion-header>\n+ <ion-header class=\"native-header\">\n+   <ion-toolbar><ion-title>Native Header</ion-title></ion-toolbar>\n+ </ion-header>\n
", "headings": [ { "id": "%E3%82%BB%E3%83%BC%E3%83%95%E3%82%A8%E3%83%AA%E3%82%A2%E7%94%A8%E3%81%AB-hidden-%E3%81%AA-header-%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B%E5%BF%85%E8%A6%81%E3%81%8C%E3%81%82%E3%82%8B%E3%81%AE%E3%81%AF%E3%81%AA%E3%81%9C%E3%81%A7%E3%81%99%E3%81%8B%EF%BC%9F", diff --git a/projects/docs/src/app/generated/projects/stripe.en.generated.ts b/projects/docs/src/app/generated/projects/stripe.en.generated.ts index 2842a93..014aa9a 100644 --- a/projects/docs/src/app/generated/projects/stripe.en.generated.ts +++ b/projects/docs/src/app/generated/projects/stripe.en.generated.ts @@ -9,7 +9,8 @@ export const PROJECT = { "category": "capacitor-plugins", "icon": "payments", "version": "8.2.1", - "description": "Native Stripe payments for Capacitor applications.", + "seoTitle": "Capacitor Stripe Plugin Documentation | rdlabo", + "description": "Integrate Stripe PaymentSheet, Apple Pay, and Google Pay in Capacitor apps with @capacitor-community/stripe for iOS, Android, and web.", "headline": "Accept Stripe payments in Capacitor apps", "overview": "Present native PaymentSheet and PaymentFlow, accept Apple Pay and Google Pay, and integrate payments on the web from the same Capacitor codebase.", "featuresHeading": "What you can build", @@ -40,6 +41,9 @@ export const PROJECT = { { "title": "Configuration", "navTitle": "Configuration", + "seoTitle": "Configure Capacitor Stripe for iOS, Android, and Web | rdlabo", + "seoDescription": "Configure @capacitor-community/stripe with a publishable key and platform settings before presenting PaymentSheet, Apple Pay, or Google Pay.", + "updatedAt": "2026-08-31", "slug": "configuration", "file": "configuration.md", "section": "Quickstart", @@ -689,6 +693,9 @@ export const PROJECT = { { "title": "Apple Pay", "navTitle": "Apple Pay", + "seoTitle": "Capacitor Stripe Apple Pay Integration | rdlabo", + "seoDescription": "Create, present, and confirm Apple Pay payments in a Capacitor app with @capacitor-community/stripe on supported Apple devices.", + "updatedAt": "2026-08-31", "slug": "apple-pay", "file": "apple-pay.md", "section": "Methods", @@ -883,6 +890,9 @@ export const PROJECT = { { "title": "Google Pay", "navTitle": "Google Pay", + "seoTitle": "Capacitor Stripe Google Pay Integration | rdlabo", + "seoDescription": "Create, present, and confirm Google Pay payments in a Capacitor app with @capacitor-community/stripe on supported Android devices.", + "updatedAt": "2026-08-31", "slug": "google-pay", "file": "google-pay.md", "section": "Methods", diff --git a/projects/docs/src/app/generated/projects/stripe.ja.generated.ts b/projects/docs/src/app/generated/projects/stripe.ja.generated.ts index 07fa9f8..ee704f6 100644 --- a/projects/docs/src/app/generated/projects/stripe.ja.generated.ts +++ b/projects/docs/src/app/generated/projects/stripe.ja.generated.ts @@ -9,7 +9,8 @@ export const PROJECT = { "category": "capacitor-plugins", "icon": "payments", "version": "8.2.1", - "description": "Capacitor アプリで Stripe のネイティブ決済を利用するためのプラグイン。", + "seoTitle": "Capacitor Stripe プラグイン ドキュメント | rdlabo", + "description": "Capacitorアプリに@capacitor-community/stripeを導入し、iOS・Android・WebでPaymentSheet、Apple Pay、Google Payを実装するためのドキュメント。", "headline": "CapacitorアプリでStripe決済を受け付ける", "overview": "同じCapacitorコードベースからネイティブのPaymentSheetとPaymentFlow、Apple Pay、Google Pay、Web決済を利用できます。", "featuresHeading": "実装できること", @@ -40,6 +41,9 @@ export const PROJECT = { { "title": "プラットフォーム設定", "navTitle": "設定", + "seoTitle": "Capacitor StripeのiOS・Android・Web設定 | rdlabo", + "seoDescription": "@capacitor-community/stripeに公開可能キーと各Platformの設定を追加し、PaymentSheet、Apple Pay、Google Payを利用する準備をします。", + "updatedAt": "2026-08-31", "slug": "configuration", "file": "configuration.md", "section": "クイックスタート", @@ -689,6 +693,9 @@ export const PROJECT = { { "title": "Apple Pay", "navTitle": "Apple Pay", + "seoTitle": "Capacitor StripeでApple Payを実装 | rdlabo", + "seoDescription": "@capacitor-community/stripeを使い、対応するApple端末のCapacitorアプリでApple Pay決済を作成・表示・確定します。", + "updatedAt": "2026-08-31", "slug": "apple-pay", "file": "apple-pay.md", "section": "メソッド", @@ -883,6 +890,9 @@ export const PROJECT = { { "title": "Google Pay", "navTitle": "Google Pay", + "seoTitle": "Capacitor StripeでGoogle Payを実装 | rdlabo", + "seoDescription": "@capacitor-community/stripeを使い、対応するAndroid端末のCapacitorアプリでGoogle Pay決済を作成・表示・確定します。", + "updatedAt": "2026-08-31", "slug": "google-pay", "file": "google-pay.md", "section": "メソッド", diff --git a/projects/docs/src/ionic-angular-scroll-header/docs/ja/ion-content.md b/projects/docs/src/ionic-angular-scroll-header/docs/ja/ion-content.md index 55d3699..e860c77 100644 --- a/projects/docs/src/ionic-angular-scroll-header/docs/ja/ion-content.md +++ b/projects/docs/src/ionic-angular-scroll-header/docs/ja/ion-content.md @@ -4,7 +4,7 @@ code: [] scrollActiveLine: [] --- -Ionic Content に Scroll 連動 Header を付けます。[インストール](/docs/readme#インストール) のあとで呼び出します。 +Ionic Content に Scroll 連動 Header を付けます。[インストール](/docs/readme#インストール) のあとで呼び出します。Safe Area用の非表示Headerと常時表示するNative Headerの使い分けは、[Safe Areaガイド](/docs/safe-area)を参照してください。 - Demo: https://rdlabo-ionic-angular-library.netlify.app/main/scroll-header - Source: https://github.com/rdlabo-dev/ionic-angular-library/blob/v21.7.0/projects/demo/src/app/scroll-header/scroll-header.page.html diff --git a/projects/docs/src/ionic-angular-scroll-header/docs/ja/safe-area.md b/projects/docs/src/ionic-angular-scroll-header/docs/ja/safe-area.md index a751828..490a0ad 100644 --- a/projects/docs/src/ionic-angular-scroll-header/docs/ja/safe-area.md +++ b/projects/docs/src/ionic-angular-scroll-header/docs/ja/safe-area.md @@ -6,6 +6,8 @@ scrollActiveLine: [] Safe Area 用の非表示 Header と常時表示 Native Header です。 +まず[IonContentガイド](/docs/ion-content)のScroll連動Headerを設定し、このページのHeader構成を用途に応じて選択してください。 + ## セーフエリア用に hidden な Header を設定する必要があるのはなぜですか? もちろん、次のように ion-content にセーフエリアを設定することもできます。 diff --git a/projects/web-site/public/sitemap.xml b/projects/web-site/public/sitemap.xml index 92bf1f3..eaf6f34 100644 --- a/projects/web-site/public/sitemap.xml +++ b/projects/web-site/public/sitemap.xml @@ -11,7 +11,8 @@ https://rdlabo.dev/articles/archive/2020 https://rdlabo.dev/articles/ionic-themes-ionic9-major-update https://rdlabo.dev/articles/ionic-theme-md3 - https://rdlabo.dev/articles/ionic-9-components-got-better + https://rdlabo.dev/articles/ionic-9-components-got-better + 2026-08-31 https://rdlabo.dev/articles/capawesome-live-update-release-cycle https://rdlabo.dev/articles/mobile-app-live-updates https://rdlabo.dev/articles/f3f96d96bd5e87 diff --git a/projects/web-site/src/app/components/article-sidebar/article-sidebar.spec.ts b/projects/web-site/src/app/components/article-sidebar/article-sidebar.spec.ts index 5964889..7346983 100644 --- a/projects/web-site/src/app/components/article-sidebar/article-sidebar.spec.ts +++ b/projects/web-site/src/app/components/article-sidebar/article-sidebar.spec.ts @@ -36,14 +36,16 @@ describe('ArticleSidebar', () => { ); expect(links.map((link) => link.textContent?.trim())).toEqual([ + 'Ionic Docs Japanese', 'Ionic Theme iOS26', 'Ionic Theme MD3', ]); expect(links.map((link) => link.getAttribute('href'))).toEqual([ + '/articles?library=ionic-docs', '/articles?library=ionic-theme-ios26', '/articles?library=ionic-theme-md3', ]); - expect(root.querySelectorAll('.article-sidebar__category-icon')).toHaveLength(2); + expect(root.querySelectorAll('.article-sidebar__category-icon')).toHaveLength(3); expect( Array.from(root.querySelectorAll('.article-sidebar__category-icon')).every( (icon) => icon.getAttribute('aria-hidden') === 'true', diff --git a/projects/web-site/src/app/generated/article-catalog.generated.ts b/projects/web-site/src/app/generated/article-catalog.generated.ts index c4e52e2..dcf2470 100644 --- a/projects/web-site/src/app/generated/article-catalog.generated.ts +++ b/projects/web-site/src/app/generated/article-catalog.generated.ts @@ -49,10 +49,18 @@ export const ARTICLE_SUMMARIES = [ "slug": "ionic-9-components-got-better", "title": "Ionic 9 Is Here! Web Standardization Takes a Big Step Forward — Five Major Changes", "description": "Ionic Framework 9 highlights five changes that matter in day-to-day app development: rich Select options, Font Icons in ion-icon, ion-img deprecation, cleaner form controls, and more accessible sheet modal handles.", + "updatedAt": "2026-08-31", "image": "https://rdlabo.dev/article-images/ionic-9-components-got-better.svg", "imageWidth": 1200, "imageHeight": 630, "emoji": "🚀", + "relatedLibraries": [ + { + "id": "ionic-docs", + "name": "Ionic Docs Japanese", + "url": "https://ionicframework.jp/docs/" + } + ], "sourceName": "Zenn", "originalUrl": "https://zenn.dev/rdlabo/articles/ionic-9-components-got-better", "publishedAt": "2026-08-20T23:21:47.000Z", diff --git a/projects/web-site/src/articles/ionic-9-components-got-better.md b/projects/web-site/src/articles/ionic-9-components-got-better.md index 00bb920..b7fe209 100644 --- a/projects/web-site/src/articles/ionic-9-components-got-better.md +++ b/projects/web-site/src/articles/ionic-9-components-got-better.md @@ -2,6 +2,9 @@ title: "Ionic 9 Is Here! Web Standardization Takes a Big Step Forward — Five Major Changes" description: "Ionic Framework 9 highlights five changes that matter in day-to-day app development: rich Select options, Font Icons in ion-icon, ion-img deprecation, cleaner form controls, and more accessible sheet modal handles." zennSlug: ionic-9-components-got-better +updatedAt: "2026-08-31" +relatedLibraries: + - ionic-docs emoji: "🚀" --- diff --git a/scripts/article-relations.test.ts b/scripts/article-relations.test.ts index fd6d8e6..e96c89b 100644 --- a/scripts/article-relations.test.ts +++ b/scripts/article-relations.test.ts @@ -1,9 +1,7 @@ import assert from 'node:assert/strict'; -import { mkdtemp, rm, writeFile } from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; +import { readFile } from 'node:fs/promises'; import test from 'node:test'; -import { loadRelatedArticlesByLibrary, parseRelatedLibraryIds } from './article-relations'; +import { groupRelatedArticlesByLibrary, parseRelatedLibraryIds } from './article-relations'; test('normalizes, deduplicates, and validates related library IDs', () => { assert.deepEqual( @@ -28,76 +26,74 @@ test('normalizes, deduplicates, and validates related library IDs', () => { ); }); -test('groups articles by library in deterministic reverse publication order', async () => { - const directory = await mkdtemp(join(tmpdir(), 'article-relations-')); - try { - await Promise.all([ - writeFile( - join(directory, 'z-last.md'), - `--- -title: Last -description: Last description -slug: last -publishedDate: "2026-08-24" -relatedLibraries: - - ionic-theme-md3 ---- -Body -`, - ), - writeFile( - join(directory, 'a-first.md'), - `--- -title: First -description: First description -zennSlug: first -publishedDate: "2026-08-23" -relatedLibraries: - - " ionic-theme-md3 " - - ionic-theme-ios26 - - ionic-theme-md3 ---- -Body -`, - ), - writeFile(join(directory, 'ignored.md'), '---\ntitle: Ignored\n---\nBody\n'), - ]); +test('groups generated article summaries by library in reverse publication order', () => { + const related = groupRelatedArticlesByLibrary([ + { + slug: 'first', + title: 'First', + description: 'First description', + publishedDate: '2026-08-23', + relatedLibraries: [ + { id: 'ionic-theme-md3' }, + { id: 'ionic-theme-ios26' }, + { id: 'ionic-theme-md3' }, + ], + }, + { + slug: 'last', + title: 'Last', + description: 'Last description', + publishedDate: '2026-08-24', + relatedLibraries: [{ id: 'ionic-theme-md3' }], + }, + { + slug: 'ignored', + title: 'Ignored', + description: 'Ignored description', + publishedDate: '2026-08-25', + }, + ]); + assert.deepEqual( + related.get('ionic-theme-md3')?.map((article) => article.slug), + ['last', 'first'], + ); + assert.deepEqual(related.get('ionic-theme-ios26'), [ + { + slug: 'first', + title: 'First', + description: 'First description', + publishedDate: '2026-08-23', + url: 'https://rdlabo.dev/articles/first', + }, + ]); +}); - const related = await loadRelatedArticlesByLibrary(directory); - assert.deepEqual( - related.get('ionic-theme-md3')?.map((article) => article.slug), - ['last', 'first'], - ); - assert.deepEqual(related.get('ionic-theme-ios26'), [ - { - slug: 'first', - title: 'First', - description: 'First description', - publishedDate: '2026-08-23', - url: 'https://rdlabo.dev/articles/first', - }, - ]); - } finally { - await rm(directory, { recursive: true, force: true }); - } +test('rejects malformed generated related article metadata', () => { + assert.throws( + () => + groupRelatedArticlesByLibrary([ + { + slug: 'invalid', + title: ' ', + description: 'Description', + publishedDate: '2026-08-24', + relatedLibraries: [{ id: 'ionic-theme-md3' }], + }, + ]), + /must declare a non-empty title/, + ); }); -test('rejects malformed related article metadata', async () => { - const directory = await mkdtemp(join(tmpdir(), 'article-relations-invalid-')); - try { - await writeFile( - join(directory, 'invalid.md'), - `--- -title: " " -description: Description -slug: invalid -relatedLibraries: - - ionic-theme-md3 ---- -`, - ); - await assert.rejects(loadRelatedArticlesByLibrary(directory), /must declare a non-empty title/); - } finally { - await rm(directory, { recursive: true, force: true }); +test('generates article metadata before documentation through the public docs entrypoint', async () => { + const packageJson = JSON.parse( + await readFile(new URL('../package.json', import.meta.url), 'utf8'), + ) as { scripts: Record }; + assert.equal( + packageJson.scripts['docs:generate'], + 'npm run articles:generate && npm run docs:generate:content', + ); + assert.equal(packageJson.scripts['docs:generate:content'], 'tsx scripts/generate-docs.ts'); + for (const script of ['prestart', 'prestart:ja', 'prebuild:docs', 'pretest']) { + assert.equal(packageJson.scripts[script], 'npm run docs:generate', script); } }); diff --git a/scripts/article-relations.ts b/scripts/article-relations.ts index fe4f758..0106583 100644 --- a/scripts/article-relations.ts +++ b/scripts/article-relations.ts @@ -1,6 +1,3 @@ -import { readFile, readdir } from 'node:fs/promises'; -import { join } from 'node:path'; -import fm from 'front-matter'; import { projectDefinitions } from './project-manifest'; export interface RelatedArticle { @@ -28,40 +25,36 @@ export function parseRelatedLibraryIds(value: unknown, file: string): string[] { return ids; } -function requiredArticleField(value: unknown, field: string, file: string): string { +interface RelatedArticleSummary { + slug: string; + title: string; + description: string; + publishedDate: string; + relatedLibraries?: readonly { id: string }[]; +} + +function requiredArticleField(value: unknown, field: string, article: string): string { if (typeof value !== 'string' || !value.trim()) { - throw new Error(`${file} with relatedLibraries must declare a non-empty ${field}`); + throw new Error(`${article} with relatedLibraries must declare a non-empty ${field}`); } return value.trim(); } -export async function loadRelatedArticlesByLibrary( - articlesDirectory: string, -): Promise> { +export function groupRelatedArticlesByLibrary( + summaries: readonly RelatedArticleSummary[], +): Map { const related = new Map(); - const files = (await readdir(articlesDirectory)) - .filter((entry) => entry.endsWith('.md')) - .sort((left, right) => left.localeCompare(right, 'en')); - - for (const file of files) { - const parsed = fm>( - await readFile(join(articlesDirectory, file), 'utf8'), + for (const summary of summaries) { + const libraryIds = parseRelatedLibraryIds( + summary.relatedLibraries?.map(({ id }) => id), + summary.slug, ); - const libraryIds = parseRelatedLibraryIds(parsed.attributes['relatedLibraries'], file); if (!libraryIds.length) continue; - const slug = requiredArticleField( - parsed.attributes['slug'] ?? parsed.attributes['zennSlug'], - 'slug', - file, - ); - const title = requiredArticleField(parsed.attributes['title'], 'title', file); - const description = requiredArticleField(parsed.attributes['description'], 'description', file); - const publishedDate = requiredArticleField( - parsed.attributes['publishedDate'], - 'publishedDate', - file, - ); + const slug = requiredArticleField(summary.slug, 'slug', summary.slug); + const title = requiredArticleField(summary.title, 'title', slug); + const description = requiredArticleField(summary.description, 'description', slug); + const publishedDate = requiredArticleField(summary.publishedDate, 'publishedDate', slug); for (const libraryId of libraryIds) { const entries = related.get(libraryId) ?? []; diff --git a/scripts/build-output.test.ts b/scripts/build-output.test.ts index 47d9e0c..70e071f 100644 --- a/scripts/build-output.test.ts +++ b/scripts/build-output.test.ts @@ -28,7 +28,7 @@ test('legacy prerender output redirects to an absolute canonical route', async ( assert.doesNotMatch(html, /\/stripe\/docs\/projects\/capacitor-stripe/); }); -test('ships permanent edge redirects for legacy Stripe documentation paths', async () => { +test('ships permanent edge redirects for canonical documentation paths', async () => { const redirects = await readFile( new URL('../dist/docs/browser/_redirects', import.meta.url), 'utf8', @@ -46,6 +46,10 @@ test('ships permanent edge redirects for legacy Stripe documentation paths', asy redirects, /^\/ja\/docs\/\* https:\/\/docs\.rdlabo\.dev\/ja\/projects\/capacitor-stripe\/docs\/:splat 301$/m, ); + assert.match( + redirects, + /^\/src\/rules\/ionic-attr-type-check\.ts https:\/\/docs\.rdlabo\.dev\/projects\/eslint-plugin-rules\/docs\/rules\/ionic-attr-type-check 301$/m, + ); }); test('prerender output includes localized SEO metadata', async () => { @@ -69,6 +73,44 @@ test('prerender output includes localized SEO metadata', async () => { assert.match(html, /"@type":"BreadcrumbList"/); }); +test('prerenders intent-focused metadata for high-impression documentation pages', async () => { + const cases = [ + { + path: 'projects/capacitor-stripe/index.html', + title: 'Capacitor Stripe Plugin Documentation | rdlabo', + description: + 'Integrate Stripe PaymentSheet, Apple Pay, and Google Pay in Capacitor apps with @capacitor-community/stripe for iOS, Android, and web.', + }, + { + path: 'projects/capacitor-stripe/docs/configuration/index.html', + title: 'Configure Capacitor Stripe for iOS, Android, and Web | rdlabo', + description: + 'Configure @capacitor-community/stripe with a publishable key and platform settings before presenting PaymentSheet, Apple Pay, or Google Pay.', + }, + { + path: 'projects/eslint-plugin-rules/docs/rules/index.html', + title: 'Angular, Ionic, and TypeScript ESLint Rules | rdlabo', + description: + 'Browse every @rdlabo/eslint-plugin-rules rule for Angular Signals, Ionic components, component boundaries, forms, and safe asynchronous code.', + }, + { + path: 'projects/capacitor-admob/docs/interstitial/index.html', + title: 'Capacitor AdMob Interstitial Ads Guide | rdlabo', + description: + 'Prepare, show, and handle interstitial ad events in Capacitor apps with @capacitor-community/admob on iOS and Android.', + }, + ] as const; + + for (const entry of cases) { + const html = await readFile( + new URL(`../dist/docs/browser/${entry.path}`, import.meta.url), + 'utf8', + ); + assert.ok(html.includes(`${entry.title}`), entry.path); + assert.ok(html.includes(` { const [english, japanese] = await Promise.all([ readFile(new URL('../dist/docs/browser/support/index.html', import.meta.url), 'utf8'), diff --git a/scripts/generate-articles.test.ts b/scripts/generate-articles.test.ts index 56291a8..672d6c4 100644 --- a/scripts/generate-articles.test.ts +++ b/scripts/generate-articles.test.ts @@ -177,6 +177,7 @@ description: Pending description zennSlug: pending relatedLibraries: - ionic-theme-md3 + - ionic-docs sourceVerification: pending sourceUrl: https://zenn.dev/rdlabo/articles/pending publishedAt: "2026-08-24T17:20:00+09:00" @@ -204,6 +205,8 @@ Translated body. assert.match(catalog, /"publishedDate": "2026-08-24"/); assert.match(catalog, /"id": "ionic-theme-md3"/); assert.match(catalog, /https:\/\/docs\.rdlabo\.dev\/projects\/ionic-theme-md3/); + assert.match(catalog, /"id": "ionic-docs"/); + assert.match(catalog, /"url": "https:\/\/ionicframework\.jp\/docs\/"/); } finally { await rm(root, { recursive: true, force: true }); } diff --git a/scripts/generate-articles.ts b/scripts/generate-articles.ts index b533da2..daa3865 100644 --- a/scripts/generate-articles.ts +++ b/scripts/generate-articles.ts @@ -321,8 +321,8 @@ function relatedLibraries(value: unknown, file: string): ArticleTranslation['rel if (!project) throw new Error(`Validated project ID disappeared: ${id}`); return { id: project.id, - name: project.shortName, - url: `https://docs.rdlabo.dev/projects/${project.slug}`, + name: project.localizedShortName?.en ?? project.shortName, + url: project.hostedUrl ?? `https://docs.rdlabo.dev/projects/${project.slug}`, }; }); } diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index 0c6b3ff..68fdaaf 100644 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -33,7 +33,8 @@ import { stripRdlaboDocsOmit, } from './package-markdown'; import { assertValidContentUpdatedAt, formatSitemapLastmod } from './seo-dates'; -import { loadRelatedArticlesByLibrary, type RelatedArticle } from './article-relations'; +import { groupRelatedArticlesByLibrary, type RelatedArticle } from './article-relations'; +import { ARTICLE_SUMMARIES } from '../projects/web-site/src/app/generated/article-catalog.generated'; import { apiMarkdown } from './docgen-api'; const root = resolve(process.cwd()); @@ -533,6 +534,7 @@ async function generateProject( title: (useFrontMatterTitle && parsed.attributes.title) || localize(page.title, locale), navTitle: localize(page.title, locale), ...(page.seoTitle ? { seoTitle: localize(page.seoTitle, locale) } : {}), + ...(page.seoDescription ? { seoDescription: localize(page.seoDescription, locale) } : {}), ...(page.updatedAt ? { updatedAt: assertValidContentUpdatedAt( @@ -582,9 +584,7 @@ async function main(): Promise { const generatedDirectory = join(root, 'projects/docs/src/app/generated'); const projectsDirectory = join(generatedDirectory, 'projects'); await mkdir(projectsDirectory, { recursive: true }); - const relatedArticlesByLibrary = await loadRelatedArticlesByLibrary( - join(root, 'projects/web-site/src/articles'), - ); + const relatedArticlesByLibrary = groupRelatedArticlesByLibrary(ARTICLE_SUMMARIES); const projectsByLocale: Record = { en: [], ja: [] }; for (const project of projectDefinitions) { for (const locale of ['en', 'ja'] as const) { @@ -609,7 +609,18 @@ async function main(): Promise { projectsByLocale[locale].map(({ pages, relatedArticles: _relatedArticles, ...project }) => ({ ...project, pages: pages.map( - ({ html, headings, codes, scrollMap, editUrl, file, ...page }: any) => page, + ({ + html, + headings, + codes, + scrollMap, + editUrl, + file, + seoTitle, + seoDescription, + updatedAt, + ...page + }: any) => page, ), })), ]), diff --git a/scripts/project-manifest.ts b/scripts/project-manifest.ts index 600df45..0912e67 100644 --- a/scripts/project-manifest.ts +++ b/scripts/project-manifest.ts @@ -12,6 +12,8 @@ export interface ProjectPageDefinition { file: string; /** Optional intent-focused document title for SEO `` metadata. */ seoTitle?: LocalizedText; + /** Optional intent-focused summary for SEO description metadata. */ + seoDescription?: LocalizedText; /** Optional explicit content update date (`YYYY-MM-DD`) for sitemap `<lastmod>`. */ updatedAt?: LocalizedText; demo?: { @@ -106,6 +108,7 @@ export const projectCategoryDefinitions: readonly ProjectCategoryDefinition[] = ]; interface PageOptions { seoTitle?: LocalizedText; + seoDescription?: LocalizedText; updatedAt?: LocalizedText; demo?: ProjectPageDefinition['demo']; } @@ -512,7 +515,17 @@ export const projectDefinitions: readonly ProjectDefinition[] = [ ), }, ), - page('Safe Area', 'Safe Area', 'safe-area', 'safe-area.md', 'Guides', 'ガイド'), + page('Safe Area', 'Safe Area', 'safe-area', 'safe-area.md', 'Guides', 'ガイド', { + seoTitle: text( + 'Ionic Safe Area with Scroll-Aware Headers | rdlabo', + 'IonicのSafe AreaとScroll連動Header | rdlabo', + ), + seoDescription: text( + 'Configure hidden safe-area headers and always-visible native headers with @rdlabo/ionic-angular-scroll-header for Ionic content layouts.', + 'Ionic・Angular CDKのScroll連動Headerで、Safe Area用の非表示Headerと常時表示するNative Headerを設定する方法を解説します。', + ), + updatedAt: text('2026-08-31', '2026-08-31'), + }), page('API', 'API', 'api', 'api.md', 'Reference', 'リファレンス'), ], }, @@ -984,9 +997,29 @@ export const projectDefinitions: readonly ProjectDefinition[] = [ 'Guide', 'ガイド', ), - page('Configuration', '設定', 'configuration', 'configuration.md', 'Guide', 'ガイド'), + page('Configuration', '設定', 'configuration', 'configuration.md', 'Guide', 'ガイド', { + seoTitle: text( + 'Configure Angular and Ionic ESLint Rules | rdlabo', + 'Angular・Ionic向けESLintルールの設定 | rdlabo', + ), + seoDescription: text( + 'Configure @rdlabo/eslint-plugin-rules with flat config presets for Angular, Ionic, or framework-independent TypeScript projects.', + '@rdlabo/eslint-plugin-rulesのFlat Configを使い、Angular・Ionic・汎用TypeScript向けのESLintプリセットを設定します。', + ), + updatedAt: text('2026-08-31', '2026-08-31'), + }), page('Migration', '移行', 'migration', 'migration.md', 'Guide', 'ガイド'), - page('Rules', 'ルール一覧', 'rules', 'rules.md', 'Reference', 'リファレンス'), + page('Rules', 'ルール一覧', 'rules', 'rules.md', 'Reference', 'リファレンス', { + seoTitle: text( + 'Angular, Ionic, and TypeScript ESLint Rules | rdlabo', + 'Angular・Ionic・TypeScript向けESLintルール一覧 | rdlabo', + ), + seoDescription: text( + 'Browse every @rdlabo/eslint-plugin-rules rule for Angular Signals, Ionic components, component boundaries, forms, and safe asynchronous code.', + 'Angular Signal、Ionic Component、Component境界、フォーム、安全な非同期処理を検査する@rdlabo/eslint-plugin-rulesのルール一覧です。', + ), + updatedAt: text('2026-08-31', '2026-08-31'), + }), page('API', 'API', 'api', 'api.md', 'Reference', 'リファレンス'), ...eslintRulePages, ], @@ -1063,9 +1096,13 @@ export const projectDefinitions: readonly ProjectDefinition[] = [ repositoryUrl: 'https://github.com/capacitor-community/stripe', category: 'capacitor-plugins', icon: 'payments', + seoTitle: text( + 'Capacitor Stripe Plugin Documentation | rdlabo', + 'Capacitor Stripe プラグイン ドキュメント | rdlabo', + ), description: text( - 'Native Stripe payments for Capacitor applications.', - 'Capacitor アプリで Stripe のネイティブ決済を利用するためのプラグイン。', + 'Integrate Stripe PaymentSheet, Apple Pay, and Google Pay in Capacitor apps with @capacitor-community/stripe for iOS, Android, and web.', + 'Capacitorアプリに@capacitor-community/stripeを導入し、iOS・Android・WebでPaymentSheet、Apple Pay、Google Payを実装するためのドキュメント。', ), headline: text( 'Accept Stripe payments in Capacitor apps', @@ -1121,6 +1158,17 @@ export const projectDefinitions: readonly ProjectDefinition[] = [ 'configuration.md', 'Quickstart', 'クイックスタート', + { + seoTitle: text( + 'Configure Capacitor Stripe for iOS, Android, and Web | rdlabo', + 'Capacitor StripeのiOS・Android・Web設定 | rdlabo', + ), + seoDescription: text( + 'Configure @capacitor-community/stripe with a publishable key and platform settings before presenting PaymentSheet, Apple Pay, or Google Pay.', + '@capacitor-community/stripeに公開可能キーと各Platformの設定を追加し、PaymentSheet、Apple Pay、Google Payを利用する準備をします。', + ), + updatedAt: text('2026-08-31', '2026-08-31'), + }, ), page( 'Vanilla JS', @@ -1164,8 +1212,28 @@ export const projectDefinitions: readonly ProjectDefinition[] = [ }, ), page('PaymentFlow', 'PaymentFlow', 'payment-flow', 'payment-flow.md', 'Methods', 'メソッド'), - page('Apple Pay', 'Apple Pay', 'apple-pay', 'apple-pay.md', 'Methods', 'メソッド'), - page('Google Pay', 'Google Pay', 'google-pay', 'google-pay.md', 'Methods', 'メソッド'), + page('Apple Pay', 'Apple Pay', 'apple-pay', 'apple-pay.md', 'Methods', 'メソッド', { + seoTitle: text( + 'Capacitor Stripe Apple Pay Integration | rdlabo', + 'Capacitor StripeでApple Payを実装 | rdlabo', + ), + seoDescription: text( + 'Create, present, and confirm Apple Pay payments in a Capacitor app with @capacitor-community/stripe on supported Apple devices.', + '@capacitor-community/stripeを使い、対応するApple端末のCapacitorアプリでApple Pay決済を作成・表示・確定します。', + ), + updatedAt: text('2026-08-31', '2026-08-31'), + }), + page('Google Pay', 'Google Pay', 'google-pay', 'google-pay.md', 'Methods', 'メソッド', { + seoTitle: text( + 'Capacitor Stripe Google Pay Integration | rdlabo', + 'Capacitor StripeでGoogle Payを実装 | rdlabo', + ), + seoDescription: text( + 'Create, present, and confirm Google Pay payments in a Capacitor app with @capacitor-community/stripe on supported Android devices.', + '@capacitor-community/stripeを使い、対応するAndroid端末のCapacitorアプリでGoogle Pay決済を作成・表示・確定します。', + ), + updatedAt: text('2026-08-31', '2026-08-31'), + }), page('API', 'API', 'api', 'api.md', 'Reference', 'リファレンス'), ], }, @@ -1377,7 +1445,17 @@ export const projectDefinitions: readonly ProjectDefinition[] = [ }, ], pages: [ - page('Getting Started', 'はじめに', 'readme', 'readme.md', 'Quickstart', 'クイックスタート'), + page('Getting Started', 'はじめに', 'readme', 'readme.md', 'Quickstart', 'クイックスタート', { + seoTitle: text( + 'Capacitor AdMob Plugin Setup Guide | rdlabo', + 'Capacitor AdMob プラグイン導入ガイド | rdlabo', + ), + seoDescription: text( + 'Install and configure @capacitor-community/admob to initialize Google Mobile Ads and display native ads in Capacitor apps on iOS and Android.', + '@capacitor-community/admobを導入し、iOS・AndroidのCapacitorアプリでGoogle Mobile Adsを初期化してネイティブ広告を表示します。', + ), + updatedAt: text('2026-08-31', '2026-08-31'), + }), page( 'Initialize', '初期化', @@ -1395,6 +1473,17 @@ export const projectDefinitions: readonly ProjectDefinition[] = [ 'interstitial.md', 'Ad formats', '広告フォーマット', + { + seoTitle: text( + 'Capacitor AdMob Interstitial Ads Guide | rdlabo', + 'Capacitor AdMobインタースティシャル広告ガイド | rdlabo', + ), + seoDescription: text( + 'Prepare, show, and handle interstitial ad events in Capacitor apps with @capacitor-community/admob on iOS and Android.', + '@capacitor-community/admobを使い、iOS・AndroidのCapacitorアプリでインタースティシャル広告の準備・表示・Event処理を実装します。', + ), + updatedAt: text('2026-08-31', '2026-08-31'), + }, ), page( 'Rewarded Ads', diff --git a/scripts/site-contract.test.ts b/scripts/site-contract.test.ts index a1d2efa..3fdd059 100644 --- a/scripts/site-contract.test.ts +++ b/scripts/site-contract.test.ts @@ -1150,7 +1150,7 @@ test('uses docs.rdlabo.dev as the canonical origin in site SEO outputs', async ( assert.doesNotMatch(robots, new RegExp(legacyOrigin.replaceAll('.', '\\.'))); }); -test('keeps legacy Stripe host paths on permanent canonical redirects', async () => { +test('keeps documentation redirects permanent and legacy Stripe hosting isolated', async () => { const [redirects, legacyRedirects, netlify] = await Promise.all([ readFile(new URL('../projects/docs/public/_redirects', import.meta.url), 'utf8'), readFile(new URL('../projects/legacy-stripe-redirect/_redirects', import.meta.url), 'utf8'), @@ -1193,6 +1193,11 @@ test('keeps legacy Stripe host paths on permanent canonical redirects', async () return { source, destination }; }); assert.deepEqual(parsedRedirects, [ + { + source: '/src/rules/ionic-attr-type-check.ts', + destination: + 'https://docs.rdlabo.dev/projects/eslint-plugin-rules/docs/rules/ionic-attr-type-check', + }, { source: '/docs/identity', destination: