From 8aab535feaeb6e7c00fa0404949c5a04f8b0e7b4 Mon Sep 17 00:00:00 2001 From: appdevelopsk Date: Mon, 26 Oct 2026 20:46:33 +0900 Subject: [PATCH 1/3] seo(toolify): keep related links within locale-specific indexable tool pages --- site/src/lib/tools/registry.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/site/src/lib/tools/registry.ts b/site/src/lib/tools/registry.ts index f5fdf6f..7672ca9 100644 --- a/site/src/lib/tools/registry.ts +++ b/site/src/lib/tools/registry.ts @@ -600,13 +600,23 @@ const INBOUND_FROM_INDEXED: Map = (() => { * 既定は false。noindex ツールのページでは noindex 同士を関連に出してよい * (利用者向けの導線であり、クロール上の損は無い)ため、絞るかは呼び出し側が決める。 */ -export function getRelated(slug: string, limit = 5, indexableOnly = false): ToolMeta[] { +export function getRelated( + slug: string, + limit = 5, + indexableOnly = false, + locale?: string, +): ToolMeta[] { const tool = SLUG_INDEX.get(slug); if (!tool) return []; - const eligible = (m: ToolMeta) => !indexableOnly || INDEXED_SLUGS.has(m.slug); + // Pages indexed as locale-specific exceptions must NOT link to related + // tools that are noindex in that locale. CORE_TOOLS_BY_DEMAND covers en/ja + // broadly but does not imply indexability for ar/th/tr/ru/es/fr. + const eligible = (m: ToolMeta) => !indexableOnly || + (locale ? isIndexableIn(locale, m.slug) : INDEXED_SLUGS.has(m.slug)); const related = tool.related .map((s) => SLUG_INDEX.get(s)) .filter((m): m is ToolMeta => Boolean(m)) + .filter((m) => m.slug !== slug) .filter(eligible); if (related.length >= limit) return related.slice(0, limit); const fill = TOOLS From cd675be026d8b20cdeec4af016c97b651ecbabbe Mon Sep 17 00:00:00 2001 From: appdevelopsk Date: Mon, 26 Oct 2026 20:46:37 +0900 Subject: [PATCH 2/3] seo(toolify): keep related links within locale-specific indexable tool pages --- site/src/app/[locale]/tools/[slug]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/app/[locale]/tools/[slug]/page.tsx b/site/src/app/[locale]/tools/[slug]/page.tsx index 4d38443..d9c89eb 100644 --- a/site/src/app/[locale]/tools/[slug]/page.tsx +++ b/site/src/app/[locale]/tools/[slug]/page.tsx @@ -64,7 +64,7 @@ export default async function ToolPage({ const tt = await getTranslations(`tools.${slug}`); // index 対象ページの関連リンクは index 対象だけに絞る(権威を noindex へ流さない)。 // noindex ページ側は従来どおり全ツールから出す — 利用者向けの導線で、クロール上の損が無い。 - const related = getRelated(slug, 6, isIndexableIn(locale, slug)); + const related = getRelated(slug, 6, isIndexableIn(locale, slug), locale); const url = `${siteConfig.url}/${locale}/tools/${slug}`; const faq = (tt.raw("faq") as { q: string; a: string }[]) ?? []; From 14cc9e71e1540e3b2c06b5973c44ddadd74c3323 Mon Sep 17 00:00:00 2001 From: appdevelopsk Date: Mon, 26 Oct 2026 20:47:15 +0900 Subject: [PATCH 3/3] test(toolify): detect locale noindex leakage in tool related links --- site/src/lib/tools/registry.test.ts | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/site/src/lib/tools/registry.test.ts b/site/src/lib/tools/registry.test.ts index 8e632a5..76f113a 100644 --- a/site/src/lib/tools/registry.test.ts +++ b/site/src/lib/tools/registry.test.ts @@ -94,3 +94,36 @@ describe("tool registry consistency", () => { } }); }); + + +describe("SEO links match the locale-specific indexability policy", () => { + it("does not recommend noindex tool URLs from indexable exception locales", async () => { + const { getRelated, isIndexableIn } = await import("./registry"); + const cases = [ + ["th", "date-calculator"], + ["tr", "grade-calculator"], + ["ar", "length-converter"], + ["ru", "pressure-converter"], + ["es", "due-date-calculator"], + ["fr", "speed-converter"], + ["en", "workdays-calculator"], + ["ja", "date-calculator"], + ] as const; + for (const [locale, slug] of cases) { + expect(isIndexableIn(locale, slug), `${locale}/${slug} should be indexable`).toBe(true); + const related = getRelated(slug, 6, true, locale); + for (const target of related) { + expect(target.slug).not.toBe(slug); + expect(isIndexableIn(locale, target.slug), + `${locale}/${slug} should not link to noindex ${target.slug}`).toBe(true); + } + expect(new Set(related.map((tool) => tool.slug)).size).toBe(related.length); + } + }); + it("leaves ordinary user navigation unrestricted on noindex pages", async () => { + const { getRelated } = await import("./registry"); + const old = getRelated("date-calculator", 6, false); + const localized = getRelated("date-calculator", 6, false, "th"); + expect(localized.map((x) => x.slug)).toEqual(old.map((x) => x.slug)); + }); +});