From 0c48927f78ecb6d66c62b4d321968466ca251d14 Mon Sep 17 00:00:00 2001 From: appdevelopsk Date: Mon, 7 Sep 2026 19:26:35 +0900 Subject: [PATCH] feat(seo/analytics): calculate event params, GA4 ranking script, trust box - GA4 tool events (calculate/copy_result/preset_click) now carry tool_slug, locale, category via shared toParams() in events.ts; ToolInteractionTracker/ToolFrame pass locale + category. Consent Mode gating in emit() unchanged. - scripts/calculate-ranking.mjs: GA4 Data API report of calculate events by customEvent:tool_slug (last 28 days), SA credentials from env. Requires event-scoped custom dimensions tool_slug/locale/category to be registered in GA4 Admin (documented in README). - TrustBox component for finance/health tools: about, not-advice, sources, last reviewed, methodology link; tool.trust strings added to all 17 locale message files. - Technical SEO audit: no bugs found (robots/sitemap/canonical/hreflang OK on production); ~0 GSC impressions is the deliberate INDEXED_LOCALES/INDEXED_SLUGS pruning policy. /ar/privacy has no redirect/reload loop. Claude-Session: https://claude.ai/code/session_01AcLxcZtf35XLpenzFXTpUC --- README.md | 16 +++ site/package.json | 1 + site/scripts/calculate-ranking.mjs | 130 ++++++++++++++++++ site/src/components/tools/ToolFrame.tsx | 9 +- .../tools/ToolInteractionTracker.tsx | 22 ++- site/src/components/tools/TrustBox.tsx | 73 ++++++++++ site/src/lib/analytics/events.ts | 28 +++- site/src/messages/ar.json | 11 +- site/src/messages/de.json | 11 +- site/src/messages/en.json | 11 +- site/src/messages/es.json | 11 +- site/src/messages/fr.json | 11 +- site/src/messages/hi.json | 11 +- site/src/messages/id.json | 11 +- site/src/messages/it.json | 11 +- site/src/messages/ja.json | 11 +- site/src/messages/ko.json | 11 +- site/src/messages/pt-BR.json | 11 +- site/src/messages/ru.json | 11 +- site/src/messages/th.json | 11 +- site/src/messages/tr.json | 11 +- site/src/messages/vi.json | 11 +- site/src/messages/zh-CN.json | 11 +- site/src/messages/zh-TW.json | 11 +- 24 files changed, 436 insertions(+), 30 deletions(-) create mode 100644 site/scripts/calculate-ranking.mjs create mode 100644 site/src/components/tools/TrustBox.tsx diff --git a/README.md b/README.md index 83bbd7cc..66720004 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,22 @@ npm run dev 2. In Claude Code, follow `pipeline/prompts/01_implement.md`, `02_seo.md`, `03_translate.md` in order 3. `cd site && npm run validate && npm run build` +## Analytics: GA4 `calculate` ranking + +Tool usage events (`calculate`, `copy_result`, `preset_click`) carry the params +`tool_slug`, `locale`, `category` (plus legacy `tool`). To report on them: + +1. GA4 Admin > Data display > Custom definitions: register **event-scoped custom + dimensions** `tool_slug`, `locale`, `category` (event parameter names identical). + Data is only collected from the registration date onwards. +2. Grant the service account *Viewer* on the GA4 property, then: + +```bash +cd site +GA4_PROPERTY_ID=123456789 GOOGLE_APPLICATION_CREDENTIALS=/path/sa.json \ + npm run report:calculate -- --days 28 --limit 50 # --by category | --json +``` + ## Documentation - [ARCHITECTURE.md](./docs/ARCHITECTURE.md) diff --git a/site/package.json b/site/package.json index 1df3104c..aea625fe 100644 --- a/site/package.json +++ b/site/package.json @@ -18,6 +18,7 @@ "test": "vitest run", "test:watch": "vitest", "indexnow": "tsx scripts/indexnow.ts", + "report:calculate": "node scripts/calculate-ranking.mjs", "postbuild": "node scripts/check-built-titles.mjs && node scripts/check-built-canonical.mjs && node scripts/check-built-descriptions.mjs" }, "dependencies": { diff --git a/site/scripts/calculate-ranking.mjs b/site/scripts/calculate-ranking.mjs new file mode 100644 index 00000000..0c55caff --- /dev/null +++ b/site/scripts/calculate-ranking.mjs @@ -0,0 +1,130 @@ +#!/usr/bin/env node +/** + * GA4 Data API から `calculate` イベントをツール別に集計し、直近 28 日のランキングを出す。 + * + * 前提(重要): GA4 Admin > Data display > Custom definitions に + * event-scoped custom dimension "tool_slug" (event parameter: tool_slug) + * を登録しておくこと。未登録だと `customEvent:tool_slug` は "(not set)" だけになる。 + * 併せて "locale" / "category" も登録すると --by locale / --by category で切れる。 + * (登録前のイベントは遡って集計されない。登録日以降のデータのみ) + * + * 認証: サービスアカウント JWT(RS256) → OAuth2 token → analyticsdata v1beta runReport。 + * 00_集客統合/growth/ga4-snapshot.mjs と同じパターン。秘密情報はすべて env から読む。 + * + * env: + * GA4_PROPERTY_ID 数値の property ID(例 123456789) + * GOOGLE_APPLICATION_CREDENTIALS サービスアカウント JSON のパス + * または GA4_SA_JSON サービスアカウント JSON 文字列(CI 向け) + * GA4_SA_EMAIL + GA4_SA_KEY client_email / private_key を個別に渡す形式も可 + * + * 使い方: + * node scripts/calculate-ranking.mjs # 28日 / tool_slug 上位 50 + * node scripts/calculate-ranking.mjs --days 7 --limit 20 + * node scripts/calculate-ranking.mjs --by category # tool_slug の代わりに category で集計 + * node scripts/calculate-ranking.mjs --json # JSON 出力(他スクリプトへパイプ) + */ +import { createSign } from "node:crypto"; +import fs from "node:fs"; + +const args = process.argv.slice(2); +function opt(name, def) { + const i = args.indexOf(`--${name}`); + return i >= 0 && args[i + 1] ? args[i + 1] : def; +} +const DAYS = Number(opt("days", "28")); +const LIMIT = Number(opt("limit", "50")); +const BY = opt("by", "tool_slug"); // tool_slug | locale | category +const JSON_OUT = args.includes("--json"); +const EVENT = opt("event", "calculate"); + +const propertyId = process.env.GA4_PROPERTY_ID; +if (!propertyId) { + console.error("GA4_PROPERTY_ID が未設定です。GA4 Admin > Property settings の数値 ID を指定してください。"); + process.exit(1); +} + +function loadServiceAccount() { + if (process.env.GA4_SA_EMAIL && process.env.GA4_SA_KEY) { + return { client_email: process.env.GA4_SA_EMAIL, private_key: process.env.GA4_SA_KEY.replace(/\\n/g, "\n") }; + } + if (process.env.GA4_SA_JSON) return JSON.parse(process.env.GA4_SA_JSON); + const p = process.env.GOOGLE_APPLICATION_CREDENTIALS; + if (p && fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, "utf8")); + console.error( + "サービスアカウント認証情報が見つかりません。GOOGLE_APPLICATION_CREDENTIALS / GA4_SA_JSON / GA4_SA_EMAIL+GA4_SA_KEY のいずれかを設定してください。", + ); + process.exit(1); +} + +function b64url(input) { + return Buffer.from(input).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); +} + +async function getAccessToken(sa) { + const now = Math.floor(Date.now() / 1000); + const header = b64url(JSON.stringify({ alg: "RS256", typ: "JWT" })); + const payload = b64url( + JSON.stringify({ + iss: sa.client_email, + scope: "https://www.googleapis.com/auth/analytics.readonly", + aud: "https://oauth2.googleapis.com/token", + iat: now, + exp: now + 3600, + }), + ); + const signer = createSign("RSA-SHA256"); + signer.update(`${header}.${payload}`); + const signature = signer.sign(sa.private_key, "base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); + const jwt = `${header}.${payload}.${signature}`; + const res = await fetch("https://oauth2.googleapis.com/token", { + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion: jwt }), + }); + if (!res.ok) throw new Error(`token: ${res.status} ${await res.text()}`); + return (await res.json()).access_token; +} + +async function runReport(token, body) { + const res = await fetch(`https://analyticsdata.googleapis.com/v1beta/properties/${propertyId}:runReport`, { + method: "POST", + headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, + body: JSON.stringify(body), + }); + if (!res.ok) throw new Error(`runReport: ${res.status} ${await res.text()}`); + return res.json(); +} + +const token = await getAccessToken(loadServiceAccount()); +const report = await runReport(token, { + dateRanges: [{ startDate: `${DAYS}daysAgo`, endDate: "today" }], + dimensions: [{ name: `customEvent:${BY}` }], + metrics: [{ name: "eventCount" }, { name: "totalUsers" }], + dimensionFilter: { filter: { fieldName: "eventName", stringFilter: { matchType: "EXACT", value: EVENT } } }, + orderBys: [{ metric: { metricName: "eventCount" }, desc: true }], + limit: LIMIT, +}); + +const rows = (report.rows ?? []).map((r) => ({ + [BY]: r.dimensionValues[0].value, + events: Number(r.metricValues[0].value), + users: Number(r.metricValues[1].value), +})); + +if (JSON_OUT) { + console.log(JSON.stringify({ event: EVENT, days: DAYS, by: BY, rows }, null, 2)); +} else { + console.log(`# ${EVENT} by ${BY} — last ${DAYS} days (top ${LIMIT})`); + if (rows.length === 0) { + console.log("(no rows) — custom dimension が未登録か、登録後にまだイベントが溜まっていない可能性があります。"); + } + const w = Math.max(8, ...rows.map((r) => String(r[BY]).length)); + console.log(`${"#".padStart(3)} ${BY.padEnd(w)} ${"events".padStart(8)} ${"users".padStart(8)}`); + rows.forEach((r, i) => { + console.log(`${String(i + 1).padStart(3)} ${String(r[BY]).padEnd(w)} ${String(r.events).padStart(8)} ${String(r.users).padStart(8)}`); + }); + const notSet = rows.find((r) => r[BY] === "(not set)"); + if (notSet && rows.length <= 1) { + console.log("\n注意: すべて (not set) です。GA4 Admin で custom dimension を登録してください。"); + } +} diff --git a/site/src/components/tools/ToolFrame.tsx b/site/src/components/tools/ToolFrame.tsx index d2679f75..3371d4c4 100644 --- a/site/src/components/tools/ToolFrame.tsx +++ b/site/src/components/tools/ToolFrame.tsx @@ -6,6 +6,7 @@ import { Breadcrumbs } from "@/components/layout/Breadcrumbs"; import { RelatedServices } from "@/components/affiliates/RelatedServices"; import { SisterSiteCta } from "@/components/cross/SisterSiteCta"; import { ToolInteractionTracker } from "./ToolInteractionTracker"; +import { TrustBox } from "./TrustBox"; import { ShareBar } from "@/components/tools/ShareBar"; import { FavoriteButton } from "@/components/tools/FavoriteButton"; import { siteConfig } from "@/lib/config"; @@ -53,7 +54,9 @@ export function ToolFrame({ meta, title, description, related, children, article 押し出されていた(個別ツール着地の engaged≒0% の構造要因)。 まず道具を見せ、広告は結果の下に置く(2026-08-22)。 */}
- {children} + + {children} +
@@ -130,9 +133,7 @@ export function ToolFrame({ meta, title, description, related, children, article {meta.category === "finance" && } {(meta.category === "finance" || meta.category === "health") && ( -

- {t(meta.category === "finance" ? "tool.financeDisclaimer" : "tool.medicalDisclaimer")} -

+ )} diff --git a/site/src/components/tools/ToolInteractionTracker.tsx b/site/src/components/tools/ToolInteractionTracker.tsx index 5c4aa9b8..14aab9e1 100644 --- a/site/src/components/tools/ToolInteractionTracker.tsx +++ b/site/src/components/tools/ToolInteractionTracker.tsx @@ -21,7 +21,19 @@ import { trackCalculate, trackCopyResult, trackPresetClick } from "@/lib/analyti * 送出は1マウントにつき各1回まで。ツールは1キーストロークごとに再計算する * ため、間引かないと1着地で数十イベントになり engagement 指標が壊れる。 */ -export function ToolInteractionTracker({ slug, children }: { slug: string; children: ReactNode }) { +export function ToolInteractionTracker({ + slug, + locale, + category, + children, +}: { + slug: string; + /** ページのロケール。GA4 custom dimension `locale` に載せる。 */ + locale?: string; + /** ToolMeta.category。GA4 custom dimension `category` に載せる。 */ + category?: string; + children: ReactNode; +}) { const ref = useRef(null); const sent = useRef({ calculate: false, copy: false, preset: false }); @@ -32,7 +44,7 @@ export function ToolInteractionTracker({ slug, children }: { slug: string; child function onInput() { if (sent.current.calculate) return; sent.current.calculate = true; - trackCalculate({ tool: slug }); + trackCalculate({ tool: slug, locale, category }); } function onClick(e: Event) { @@ -46,7 +58,7 @@ export function ToolInteractionTracker({ slug, children }: { slug: string; child if (/copy|コピー|copiar|kopieren|copier|копи|复制|複製|복사/.test(label)) { if (sent.current.copy) return; sent.current.copy = true; - trackCopyResult({ tool: slug }); + trackCopyResult({ tool: slug, locale, category }); return; } @@ -56,7 +68,7 @@ export function ToolInteractionTracker({ slug, children }: { slug: string; child if (/preset|sample|example|プリセット|サンプル|例|ejemplo|beispiel|exemple|пример|示例|範例|예시/.test(label)) { if (sent.current.preset) return; sent.current.preset = true; - trackPresetClick({ tool: slug }); + trackPresetClick({ tool: slug, locale, category }); } } @@ -68,7 +80,7 @@ export function ToolInteractionTracker({ slug, children }: { slug: string; child el.removeEventListener("change", onInput); el.removeEventListener("click", onClick); }; - }, [slug]); + }, [slug, locale, category]); return
{children}
; } diff --git a/site/src/components/tools/TrustBox.tsx b/site/src/components/tools/TrustBox.tsx new file mode 100644 index 00000000..d6527c3a --- /dev/null +++ b/site/src/components/tools/TrustBox.tsx @@ -0,0 +1,73 @@ +import { useLocale, useTranslations } from "next-intl"; +import { Link } from "@/lib/i18n/navigation"; +import type { ToolCategory, ToolMeta } from "@/lib/tools/types"; + +/** + * 健康 / 金融ツール向けの信頼性ボックス(YMYL 対応)。 + * + * 「このツールについて / 医療・金融助言ではない / 出典 / 最終レビュー日」を + * 1つのカードにまとめて表示する。ToolFrame から category が health | finance の + * ときだけ描画されるため、223 本の各ツールを触らずに一括適用できる。 + * 文言は messages/.json の tool.trust.* と既存の + * tool.medicalDisclaimer / tool.financeDisclaimer を使う。 + */ +export function TrustBox({ + category, + sources, + updatedAt, +}: { + category: ToolCategory; + sources?: ToolMeta["sources"]; + updatedAt: string; +}) { + const t = useTranslations("tool"); + const locale = useLocale(); + const isFinance = category === "finance"; + const reviewed = new Date(updatedAt); + const reviewedLabel = Number.isNaN(reviewed.getTime()) + ? updatedAt + : new Intl.DateTimeFormat(locale, { year: "numeric", month: "long", day: "numeric" }).format(reviewed); + + return ( +
+

+ {t("trust.title")} +

+

{t(isFinance ? "trust.aboutFinance" : "trust.aboutHealth")}

+

+ {t("trust.notAdvice")}: + {t(isFinance ? "financeDisclaimer" : "medicalDisclaimer")} +

+
+
{t("trust.sources")}
+
+ {sources && sources.length > 0 ? ( + + ) : ( + {t("trust.noSources")} + )} +
+
{t("trust.lastReviewed")}
+
+ +
+
+

+ + {t("methodologyLink")} + +

+
+ ); +} diff --git a/site/src/lib/analytics/events.ts b/site/src/lib/analytics/events.ts index 209051ba..61ce0389 100644 --- a/site/src/lib/analytics/events.ts +++ b/site/src/lib/analytics/events.ts @@ -13,10 +13,19 @@ import { siteConfig } from "@/lib/config"; * ガードは PageViewTracker と同一: gaId 未設定 / SSR / gtag 未ロードでは黙って no-op。 * Consent Mode v2 は gtag.tsx 側で既定 denied(EEA) を設定済みのため、 * ここで同意状態を再判定する必要はない(gtag 側が保留・破棄を行う)。 + * + * パラメータ(2026-09-07): `tool_slug` / `locale` / `category` を追加。 + * GA4 で集計するには Admin > Custom definitions に event-scoped custom dimension + * として登録が必要(未登録だと Data API の customEvent:tool_slug が空になる)。 + * 旧 `tool` パラメータは既存レポート互換のため当面併送する。 */ export type ToolEventParams = { - /** ツールの slug。GA4 では custom dimension として使う。 */ + /** ツールの slug。GA4 では custom dimension `tool_slug` として使う。 */ tool: string; + /** ページのロケール(en / ja / ar ...)。custom dimension `locale`。 */ + locale?: string; + /** ToolMeta.category(health / finance / ...)。custom dimension `category`。 */ + category?: string; /** 任意の補足(モード名・プリセット名など)。カーディナリティを抑えるため短い識別子のみ。 */ label?: string; }; @@ -27,17 +36,28 @@ function emit(name: string, params: Record): void { window.gtag("event", name, params); } +/** ToolEventParams を GA4 送出用の params に正規化する(undefined は落とす)。 */ +function toParams(p: ToolEventParams): Record { + return { + tool: p.tool, + tool_slug: p.tool, + ...(p.locale ? { locale: p.locale } : {}), + ...(p.category ? { category: p.category } : {}), + ...(p.label ? { label: p.label } : {}), + }; +} + /** ツールが有効な入力で結果を算出したとき。ツール1着地につき最大1回に間引くこと。 */ export function trackCalculate(p: ToolEventParams): void { - emit("calculate", { tool: p.tool, ...(p.label ? { label: p.label } : {}) }); + emit("calculate", toParams(p)); } /** 結果(またはコード/URL)をクリップボードへコピーしたとき。 */ export function trackCopyResult(p: ToolEventParams): void { - emit("copy_result", { tool: p.tool, ...(p.label ? { label: p.label } : {}) }); + emit("copy_result", toParams(p)); } /** プリセット/サンプル/例示ボタンを押したとき。 */ export function trackPresetClick(p: ToolEventParams): void { - emit("preset_click", { tool: p.tool, ...(p.label ? { label: p.label } : {}) }); + emit("preset_click", toParams(p)); } diff --git a/site/src/messages/ar.json b/site/src/messages/ar.json index eeb90b10..016d0da6 100644 --- a/site/src/messages/ar.json +++ b/site/src/messages/ar.json @@ -59,7 +59,16 @@ "search": "ابحث في {n} أداة…", "searchNoResults": "لم يتم العثور على أدوات. جرّب كلمة مفتاحية أخرى.", "searchResults": "تم العثور على {n} أداة", - "copyResultLink": "نسخ رابط النتيجة" + "copyResultLink": "نسخ رابط النتيجة", + "trust": { + "title": "حول هذه الحاسبة", + "aboutHealth": "تستخدم هذه الحاسبة معادلات وقيمًا مرجعية منشورة من المصادر المذكورة أدناه. النتائج تقديرية وتعتمد على دقة مدخلاتك، ولا تأخذ في الاعتبار تاريخك الطبي الفردي.", + "aboutFinance": "تستخدم هذه الحاسبة معادلات مالية قياسية. النتائج تقديرية فقط؛ وتختلف الأسعار والرسوم والضرائب وقواعد التقريب الفعلية حسب مزود الخدمة والبلد.", + "notAdvice": "تنبيه مهم", + "sources": "المصادر", + "noSources": "تستند إلى معادلات قياسية شائعة الاستخدام. راجع صفحة المنهجية لمعرفة كيفية بناء الأدوات ومراجعتها.", + "lastReviewed": "آخر مراجعة" + } }, "consent": { "title": "ملفات تعريف الارتباط والإعلانات", diff --git a/site/src/messages/de.json b/site/src/messages/de.json index 9d9afdc5..63020c59 100644 --- a/site/src/messages/de.json +++ b/site/src/messages/de.json @@ -59,7 +59,16 @@ "search": "{n} Tools durchsuchen…", "searchNoResults": "Keine Tools gefunden. Versuchen Sie ein anderes Stichwort.", "searchResults": "{n} Tools gefunden", - "copyResultLink": "Ergebnis-Link kopieren" + "copyResultLink": "Ergebnis-Link kopieren", + "trust": { + "title": "Über diesen Rechner", + "aboutHealth": "Dieser Rechner verwendet veröffentlichte Formeln und Referenzwerte aus den unten genannten Quellen. Die Ergebnisse sind Schätzungen, hängen von der Genauigkeit Ihrer Eingaben ab und berücksichtigen nicht Ihre individuelle Krankengeschichte.", + "aboutFinance": "Dieser Rechner verwendet Standard-Finanzformeln. Die Ergebnisse sind nur Schätzungen; tatsächliche Zinssätze, Gebühren, Steuern und Rundungsregeln variieren je nach Anbieter und Land.", + "notAdvice": "Wichtig", + "sources": "Quellen", + "noSources": "Basiert auf weit verbreiteten Standardformeln. Wie unsere Tools erstellt und geprüft werden, erfahren Sie auf der Methodik-Seite.", + "lastReviewed": "Zuletzt geprüft" + } }, "consent": { "title": "Cookies und Werbung", diff --git a/site/src/messages/en.json b/site/src/messages/en.json index 7853c742..939131f5 100644 --- a/site/src/messages/en.json +++ b/site/src/messages/en.json @@ -59,7 +59,16 @@ "search": "Search {n} tools…", "searchNoResults": "No tools matched. Try another keyword.", "searchResults": "{n} tools found", - "copyResultLink": "Copy result link" + "copyResultLink": "Copy result link", + "trust": { + "title": "About this calculator", + "aboutHealth": "This calculator uses published formulas and reference values from the sources listed below. Results are estimates and depend on the accuracy of your inputs; they cannot account for your individual medical history.", + "aboutFinance": "This calculator uses standard financial formulas. Results are estimates only; actual rates, fees, taxes and rounding rules vary by provider and country.", + "notAdvice": "Important", + "sources": "Sources", + "noSources": "Based on widely used standard formulas. See our methodology page for how tools are built and reviewed.", + "lastReviewed": "Last reviewed" + } }, "consent": { "title": "Cookies & ads", diff --git a/site/src/messages/es.json b/site/src/messages/es.json index 87c1c7a6..ce9be589 100644 --- a/site/src/messages/es.json +++ b/site/src/messages/es.json @@ -59,7 +59,16 @@ "search": "Buscar entre {n} herramientas…", "searchNoResults": "No se encontraron herramientas. Prueba otra palabra clave.", "searchResults": "{n} herramientas encontradas", - "copyResultLink": "Copiar enlace del resultado" + "copyResultLink": "Copiar enlace del resultado", + "trust": { + "title": "Acerca de esta calculadora", + "aboutHealth": "Esta calculadora utiliza fórmulas y valores de referencia publicados en las fuentes indicadas abajo. Los resultados son estimaciones que dependen de la exactitud de tus datos y no tienen en cuenta tu historial médico personal.", + "aboutFinance": "Esta calculadora utiliza fórmulas financieras estándar. Los resultados son solo estimaciones; las tasas, comisiones, impuestos y reglas de redondeo reales varían según la entidad y el país.", + "notAdvice": "Importante", + "sources": "Fuentes", + "noSources": "Basado en fórmulas estándar de uso generalizado. Consulta nuestra página de metodología para saber cómo se crean y revisan las herramientas.", + "lastReviewed": "Última revisión" + } }, "consent": { "title": "Cookies y anuncios", diff --git a/site/src/messages/fr.json b/site/src/messages/fr.json index 8a7bbe39..81498ab5 100644 --- a/site/src/messages/fr.json +++ b/site/src/messages/fr.json @@ -59,7 +59,16 @@ "search": "Rechercher parmi {n} outils…", "searchNoResults": "Aucun outil trouvé. Essayez un autre mot-clé.", "searchResults": "{n} outils trouvés", - "copyResultLink": "Copier le lien du résultat" + "copyResultLink": "Copier le lien du résultat", + "trust": { + "title": "À propos de ce calculateur", + "aboutHealth": "Ce calculateur utilise des formules et valeurs de référence publiées, issues des sources indiquées ci-dessous. Les résultats sont des estimations qui dépendent de l'exactitude de vos données et ne tiennent pas compte de vos antécédents médicaux.", + "aboutFinance": "Ce calculateur utilise des formules financières standard. Les résultats ne sont que des estimations ; les taux, frais, impôts et règles d'arrondi réels varient selon l'établissement et le pays.", + "notAdvice": "Important", + "sources": "Sources", + "noSources": "Basé sur des formules standard largement utilisées. Consultez notre page méthodologie pour savoir comment les outils sont conçus et vérifiés.", + "lastReviewed": "Dernière vérification" + } }, "consent": { "title": "Cookies et publicité", diff --git a/site/src/messages/hi.json b/site/src/messages/hi.json index 92eb65f1..68a6969b 100644 --- a/site/src/messages/hi.json +++ b/site/src/messages/hi.json @@ -59,7 +59,16 @@ "search": "{n} टूल्स में खोजें…", "searchNoResults": "कोई टूल नहीं मिला। कोई दूसरा कीवर्ड आज़माएँ।", "searchResults": "{n} टूल मिले", - "copyResultLink": "परिणाम लिंक कॉपी करें" + "copyResultLink": "परिणाम लिंक कॉपी करें", + "trust": { + "title": "इस कैलकुलेटर के बारे में", + "aboutHealth": "यह कैलकुलेटर नीचे दिए गए स्रोतों में प्रकाशित सूत्रों और संदर्भ मानों का उपयोग करता है। परिणाम अनुमान हैं, आपके इनपुट की सटीकता पर निर्भर करते हैं और आपके व्यक्तिगत चिकित्सा इतिहास को ध्यान में नहीं रखते।", + "aboutFinance": "यह कैलकुलेटर मानक वित्तीय सूत्रों का उपयोग करता है। परिणाम केवल अनुमान हैं; वास्तविक दरें, शुल्क, कर और राउंडिंग नियम संस्था और देश के अनुसार भिन्न होते हैं।", + "notAdvice": "महत्वपूर्ण", + "sources": "स्रोत", + "noSources": "व्यापक रूप से उपयोग किए जाने वाले मानक सूत्रों पर आधारित। टूल कैसे बनाए और समीक्षा किए जाते हैं, यह जानने के लिए हमारा कार्यप्रणाली पृष्ठ देखें।", + "lastReviewed": "अंतिम समीक्षा" + } }, "consent": { "title": "कुकीज़ और विज्ञापन", diff --git a/site/src/messages/id.json b/site/src/messages/id.json index 9c383266..a81240ef 100644 --- a/site/src/messages/id.json +++ b/site/src/messages/id.json @@ -59,7 +59,16 @@ "search": "Cari {n} alat…", "searchNoResults": "Tidak ada alat yang cocok. Coba kata kunci lain.", "searchResults": "{n} alat ditemukan", - "copyResultLink": "Salin tautan hasil" + "copyResultLink": "Salin tautan hasil", + "trust": { + "title": "Tentang kalkulator ini", + "aboutHealth": "Kalkulator ini menggunakan rumus dan nilai referensi yang dipublikasikan dari sumber di bawah. Hasilnya berupa perkiraan, bergantung pada keakuratan data yang Anda masukkan, dan tidak mempertimbangkan riwayat kesehatan pribadi Anda.", + "aboutFinance": "Kalkulator ini menggunakan rumus keuangan standar. Hasilnya hanya perkiraan; suku bunga, biaya, pajak, dan aturan pembulatan sebenarnya berbeda menurut penyedia dan negara.", + "notAdvice": "Penting", + "sources": "Sumber", + "noSources": "Berdasarkan rumus standar yang umum digunakan. Lihat halaman metodologi kami untuk mengetahui cara alat dibuat dan ditinjau.", + "lastReviewed": "Terakhir ditinjau" + } }, "consent": { "title": "Cookie & iklan", diff --git a/site/src/messages/it.json b/site/src/messages/it.json index 65ce7af9..e4073d65 100644 --- a/site/src/messages/it.json +++ b/site/src/messages/it.json @@ -59,7 +59,16 @@ "search": "Cerca tra {n} strumenti…", "searchNoResults": "Nessuno strumento trovato. Prova un'altra parola chiave.", "searchResults": "{n} strumenti trovati", - "copyResultLink": "Copia link del risultato" + "copyResultLink": "Copia link del risultato", + "trust": { + "title": "Informazioni su questo calcolatore", + "aboutHealth": "Questo calcolatore utilizza formule e valori di riferimento pubblicati nelle fonti elencate di seguito. I risultati sono stime che dipendono dall'accuratezza dei dati inseriti e non tengono conto della tua storia clinica personale.", + "aboutFinance": "Questo calcolatore utilizza formule finanziarie standard. I risultati sono solo stime; tassi, commissioni, imposte e regole di arrotondamento reali variano in base all'istituto e al paese.", + "notAdvice": "Importante", + "sources": "Fonti", + "noSources": "Basato su formule standard ampiamente utilizzate. Consulta la pagina sulla metodologia per sapere come vengono creati e verificati gli strumenti.", + "lastReviewed": "Ultima revisione" + } }, "consent": { "title": "Cookie e annunci", diff --git a/site/src/messages/ja.json b/site/src/messages/ja.json index b2ff9278..326d36fa 100644 --- a/site/src/messages/ja.json +++ b/site/src/messages/ja.json @@ -59,7 +59,16 @@ "search": "{n}個のツールを検索…", "searchNoResults": "該当するツールがありません。別のキーワードをお試しください。", "searchResults": "{n}件のツール", - "copyResultLink": "結果リンクをコピー" + "copyResultLink": "結果リンクをコピー", + "trust": { + "title": "この計算ツールについて", + "aboutHealth": "この計算ツールは、下記の出典に基づく公開された計算式・基準値を用いています。結果は入力値に依存する推定値であり、個人の病歴や体質は考慮されません。", + "aboutFinance": "この計算ツールは一般的な金融計算式を用いています。結果はあくまで目安であり、実際の金利・手数料・税金・端数処理は金融機関や国によって異なります。", + "notAdvice": "ご注意", + "sources": "出典", + "noSources": "広く用いられている標準的な計算式に基づいています。ツールの作成・レビュー方法はメソドロジーページをご覧ください。", + "lastReviewed": "最終レビュー日" + } }, "consent": { "title": "Cookieと広告について", diff --git a/site/src/messages/ko.json b/site/src/messages/ko.json index 843158a1..50ed7627 100644 --- a/site/src/messages/ko.json +++ b/site/src/messages/ko.json @@ -59,7 +59,16 @@ "search": "{n}개 도구 검색…", "searchNoResults": "일치하는 도구가 없습니다. 다른 키워드를 시도해 보세요.", "searchResults": "{n}개의 도구", - "copyResultLink": "결과 링크 복사" + "copyResultLink": "결과 링크 복사", + "trust": { + "title": "이 계산기 안내", + "aboutHealth": "이 계산기는 아래 출처에 게시된 공식과 기준값을 사용합니다. 결과는 입력값의 정확도에 따라 달라지는 추정치이며 개인의 병력은 고려되지 않습니다.", + "aboutFinance": "이 계산기는 표준 금융 공식을 사용합니다. 결과는 참고용 추정치이며 실제 금리·수수료·세금·반올림 규칙은 금융기관과 국가에 따라 다릅니다.", + "notAdvice": "유의사항", + "sources": "출처", + "noSources": "널리 사용되는 표준 공식을 기반으로 합니다. 도구의 제작 및 검토 방법은 방법론 페이지를 참고하세요.", + "lastReviewed": "최종 검토일" + } }, "consent": { "title": "쿠키 및 광고", diff --git a/site/src/messages/pt-BR.json b/site/src/messages/pt-BR.json index d56f2ff5..e22ee8e4 100644 --- a/site/src/messages/pt-BR.json +++ b/site/src/messages/pt-BR.json @@ -59,7 +59,16 @@ "search": "Buscar entre {n} ferramentas…", "searchNoResults": "Nenhuma ferramenta encontrada. Tente outra palavra-chave.", "searchResults": "{n} ferramentas encontradas", - "copyResultLink": "Copiar link do resultado" + "copyResultLink": "Copiar link do resultado", + "trust": { + "title": "Sobre esta calculadora", + "aboutHealth": "Esta calculadora usa fórmulas e valores de referência publicados nas fontes listadas abaixo. Os resultados são estimativas que dependem da precisão dos seus dados e não consideram seu histórico médico individual.", + "aboutFinance": "Esta calculadora usa fórmulas financeiras padrão. Os resultados são apenas estimativas; taxas, tarifas, impostos e regras de arredondamento reais variam conforme a instituição e o país.", + "notAdvice": "Importante", + "sources": "Fontes", + "noSources": "Baseado em fórmulas padrão amplamente utilizadas. Veja nossa página de metodologia para saber como as ferramentas são criadas e revisadas.", + "lastReviewed": "Última revisão" + } }, "consent": { "title": "Cookies e anúncios", diff --git a/site/src/messages/ru.json b/site/src/messages/ru.json index 6764f993..3d91354f 100644 --- a/site/src/messages/ru.json +++ b/site/src/messages/ru.json @@ -59,7 +59,16 @@ "search": "Поиск по {n} инструментам…", "searchNoResults": "Инструменты не найдены. Попробуйте другое ключевое слово.", "searchResults": "Найдено инструментов: {n}", - "copyResultLink": "Копировать ссылку на результат" + "copyResultLink": "Копировать ссылку на результат", + "trust": { + "title": "Об этом калькуляторе", + "aboutHealth": "Калькулятор использует опубликованные формулы и справочные значения из указанных ниже источников. Результаты являются оценочными, зависят от точности введённых данных и не учитывают вашу индивидуальную историю болезни.", + "aboutFinance": "Калькулятор использует стандартные финансовые формулы. Результаты носят оценочный характер; реальные ставки, комиссии, налоги и правила округления зависят от организации и страны.", + "notAdvice": "Важно", + "sources": "Источники", + "noSources": "Основано на общепринятых стандартных формулах. О том, как создаются и проверяются инструменты, читайте на странице методологии.", + "lastReviewed": "Последняя проверка" + } }, "consent": { "title": "Куки и реклама", diff --git a/site/src/messages/th.json b/site/src/messages/th.json index df71f0b9..d65d4dfe 100644 --- a/site/src/messages/th.json +++ b/site/src/messages/th.json @@ -59,7 +59,16 @@ "search": "ค้นหาเครื่องมือ {n} รายการ…", "searchNoResults": "ไม่พบเครื่องมือที่ตรงกัน ลองใช้คำค้นอื่น", "searchResults": "พบ {n} เครื่องมือ", - "copyResultLink": "คัดลอกลิงก์ผลลัพธ์" + "copyResultLink": "คัดลอกลิงก์ผลลัพธ์", + "trust": { + "title": "เกี่ยวกับเครื่องคำนวณนี้", + "aboutHealth": "เครื่องคำนวณนี้ใช้สูตรและค่าอ้างอิงที่เผยแพร่จากแหล่งข้อมูลด้านล่าง ผลลัพธ์เป็นค่าประมาณที่ขึ้นอยู่กับความถูกต้องของข้อมูลที่กรอก และไม่ได้คำนึงถึงประวัติสุขภาพส่วนบุคคลของคุณ", + "aboutFinance": "เครื่องคำนวณนี้ใช้สูตรทางการเงินมาตรฐาน ผลลัพธ์เป็นเพียงค่าประมาณ อัตราดอกเบี้ย ค่าธรรมเนียม ภาษี และการปัดเศษจริงแตกต่างกันไปตามผู้ให้บริการและประเทศ", + "notAdvice": "ข้อควรทราบ", + "sources": "แหล่งข้อมูล", + "noSources": "อ้างอิงจากสูตรมาตรฐานที่ใช้กันอย่างแพร่หลาย ดูวิธีสร้างและตรวจสอบเครื่องมือได้ที่หน้าระเบียบวิธี", + "lastReviewed": "ตรวจสอบล่าสุด" + } }, "consent": { "title": "คุกกี้และโฆษณา", diff --git a/site/src/messages/tr.json b/site/src/messages/tr.json index 78d103fb..13a82633 100644 --- a/site/src/messages/tr.json +++ b/site/src/messages/tr.json @@ -59,7 +59,16 @@ "search": "{n} araç içinde ara…", "searchNoResults": "Eşleşen araç bulunamadı. Başka bir anahtar kelime deneyin.", "searchResults": "{n} araç bulundu", - "copyResultLink": "Sonuç bağlantısını kopyala" + "copyResultLink": "Sonuç bağlantısını kopyala", + "trust": { + "title": "Bu hesaplayıcı hakkında", + "aboutHealth": "Bu hesaplayıcı, aşağıda listelenen kaynaklardaki yayımlanmış formülleri ve referans değerleri kullanır. Sonuçlar tahminidir, girdilerinizin doğruluğuna bağlıdır ve kişisel tıbbi geçmişinizi dikkate almaz.", + "aboutFinance": "Bu hesaplayıcı standart finansal formüller kullanır. Sonuçlar yalnızca tahminidir; gerçek faiz oranları, ücretler, vergiler ve yuvarlama kuralları kuruma ve ülkeye göre değişir.", + "notAdvice": "Önemli", + "sources": "Kaynaklar", + "noSources": "Yaygın olarak kullanılan standart formüllere dayanır. Araçların nasıl oluşturulup incelendiğini metodoloji sayfamızda bulabilirsiniz.", + "lastReviewed": "Son inceleme" + } }, "consent": { "title": "Çerezler ve reklamlar", diff --git a/site/src/messages/vi.json b/site/src/messages/vi.json index 3d860d3d..7c5ae238 100644 --- a/site/src/messages/vi.json +++ b/site/src/messages/vi.json @@ -59,7 +59,16 @@ "search": "Tìm trong {n} công cụ…", "searchNoResults": "Không tìm thấy công cụ nào. Hãy thử từ khóa khác.", "searchResults": "Tìm thấy {n} công cụ", - "copyResultLink": "Sao chép liên kết kết quả" + "copyResultLink": "Sao chép liên kết kết quả", + "trust": { + "title": "Về công cụ tính này", + "aboutHealth": "Công cụ này sử dụng các công thức và giá trị tham chiếu đã công bố từ các nguồn liệt kê bên dưới. Kết quả chỉ là ước tính, phụ thuộc vào độ chính xác của dữ liệu bạn nhập và không xét đến tiền sử bệnh cá nhân của bạn.", + "aboutFinance": "Công cụ này sử dụng các công thức tài chính tiêu chuẩn. Kết quả chỉ mang tính ước tính; lãi suất, phí, thuế và quy tắc làm tròn thực tế khác nhau tùy tổ chức và quốc gia.", + "notAdvice": "Lưu ý", + "sources": "Nguồn", + "noSources": "Dựa trên các công thức tiêu chuẩn được sử dụng rộng rãi. Xem trang phương pháp của chúng tôi để biết cách công cụ được xây dựng và kiểm tra.", + "lastReviewed": "Kiểm tra lần cuối" + } }, "consent": { "title": "Cookie và quảng cáo", diff --git a/site/src/messages/zh-CN.json b/site/src/messages/zh-CN.json index 8f7e100b..855f9f1c 100644 --- a/site/src/messages/zh-CN.json +++ b/site/src/messages/zh-CN.json @@ -59,7 +59,16 @@ "search": "搜索 {n} 个工具…", "searchNoResults": "未找到匹配的工具。请尝试其他关键词。", "searchResults": "找到 {n} 个工具", - "copyResultLink": "复制结果链接" + "copyResultLink": "复制结果链接", + "trust": { + "title": "关于此计算器", + "aboutHealth": "本计算器采用下列来源公布的公式和参考值。结果为估算值,取决于您输入数据的准确性,且无法考虑您的个人病史。", + "aboutFinance": "本计算器采用标准金融公式。结果仅为估算;实际利率、费用、税费及四舍五入规则因机构和国家/地区而异。", + "notAdvice": "重要提示", + "sources": "来源", + "noSources": "基于广泛使用的标准公式。有关工具的构建与审核方式,请参阅方法说明页面。", + "lastReviewed": "最近审核" + } }, "consent": { "title": "Cookie 与广告", diff --git a/site/src/messages/zh-TW.json b/site/src/messages/zh-TW.json index 9942db99..c960a265 100644 --- a/site/src/messages/zh-TW.json +++ b/site/src/messages/zh-TW.json @@ -59,7 +59,16 @@ "search": "搜尋 {n} 個工具…", "searchNoResults": "找不到符合的工具。請嘗試其他關鍵字。", "searchResults": "找到 {n} 個工具", - "copyResultLink": "複製結果連結" + "copyResultLink": "複製結果連結", + "trust": { + "title": "關於此計算器", + "aboutHealth": "本計算器採用下列來源公布的公式與參考值。結果為估算值,取決於您輸入資料的準確性,且無法考量您的個人病史。", + "aboutFinance": "本計算器採用標準金融公式。結果僅為估算;實際利率、費用、稅金及進位規則因機構與國家/地區而異。", + "notAdvice": "重要提醒", + "sources": "來源", + "noSources": "基於廣泛使用的標準公式。有關工具的建置與審核方式,請參閱方法說明頁面。", + "lastReviewed": "最近審核" + } }, "consent": { "title": "Cookie 與廣告",