@tomako/tools-runtime 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -6
- package/src/components/tomato-logo.tsx +71 -0
- package/src/components/tools/workspace/tool-workspace-layout.tsx +1 -1
- package/src/constants/related-tools.ts +4 -0
- package/src/features/tools/app-icon-resizer/app-icon-resizer.container.tsx +15 -2
- package/src/features/tools/app-icon-resizer/app-icon-resizer.spec.ts +1 -1
- package/src/features/tools/app-icon-resizer/widget/app-icon-resizer.tsx +54 -11
- package/src/features/tools/app-icon-resizer/widget/platform-icons.tsx +2 -5
- package/src/features/tools/mrr-calculator/mrr-calculator.container.tsx +23 -0
- package/src/features/tools/mrr-calculator/mrr-calculator.spec.ts +15 -0
- package/src/features/tools/mrr-calculator/widget/calculations.ts +74 -0
- package/src/features/tools/mrr-calculator/widget/index.ts +1 -0
- package/src/features/tools/mrr-calculator/widget/mrr-calculator.tsx +581 -0
- package/src/features/tools/registry.ts +6 -0
- package/src/features/tools/runtime-registry.ts +4 -0
- package/src/features/tools/shared/create-standard-landing-container.tsx +35 -5
- package/src/features/tools/shared/tool-page-shell.tsx +10 -137
- package/src/features/tools/shared/tool-standard-landing-sections.tsx +262 -52
- package/src/features/tools/spec-registry.ts +2 -0
- package/src/features/tools/tarot-reading/tarot-reading.container.tsx +1 -22
- package/src/features/tools/widget-registry.ts +2 -0
- package/src/i18n/messages/en/tools/app-icon-resizer.ts +90 -62
- package/src/i18n/messages/en/tools/index.ts +2 -0
- package/src/i18n/messages/en/tools/mrr-calculator.ts +202 -0
- package/src/i18n/messages/en/tools-ui.ts +3 -2
- package/src/i18n/messages/zh/page/tools.ts +2 -3
- package/src/i18n/messages/zh/tools/app-icon-resizer.ts +90 -62
- package/src/i18n/messages/zh/tools/index.ts +2 -0
- package/src/i18n/messages/zh/tools/mrr-calculator.ts +112 -0
- package/src/i18n/messages/zh/tools-ui.ts +3 -2
- package/src/i18n/messages/zh-tw/tools/app-icon-resizer.ts +90 -62
- package/src/i18n/messages/zh-tw/tools/index.ts +2 -0
- package/src/i18n/messages/zh-tw/tools/mrr-calculator.ts +52 -0
- package/src/i18n/messages/zh-tw/tools-ui.ts +3 -2
- package/src/lib/tools/resolve-tool-locale-copy.ts +7 -1
|
@@ -14,6 +14,7 @@ import marketForecastReport from "./market-forecast-report";
|
|
|
14
14
|
import kokoCodexAssistant from "./koko-codex-assistant";
|
|
15
15
|
import logoGenerator from "./logo-generator";
|
|
16
16
|
import millionUserGrowthTest from "./million-user-growth-test";
|
|
17
|
+
import mrrCalculator from "./mrr-calculator";
|
|
17
18
|
import ogImageGenerator from "./og-image-generator";
|
|
18
19
|
import pitchDeckGenerator from "./pitch-deck-generator";
|
|
19
20
|
import productAgreementGenerator from "./product-agreement-generator";
|
|
@@ -59,6 +60,7 @@ const toolsPages = {
|
|
|
59
60
|
kokoCodexAssistant,
|
|
60
61
|
logoGenerator,
|
|
61
62
|
millionUserGrowthTest,
|
|
63
|
+
mrrCalculator,
|
|
62
64
|
ogImageGenerator,
|
|
63
65
|
appStoreScreenshotGenerator,
|
|
64
66
|
twitterGifDownloader,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const mrrCalculator = {
|
|
2
|
+
meta: {
|
|
3
|
+
title: "SaaS MRR 計算器|每月經常性收入計算",
|
|
4
|
+
description: "用客戶資料計算 MRR 和 ARR,或根據公開網站流量、方案價格和可調整假設估算 SaaS MRR。",
|
|
5
|
+
intro: "把月繳、季繳或年繳訂閱統一換算為每月經常性收入,也可以切換到公開資料估算模式。",
|
|
6
|
+
category: "SaaS 指標工具",
|
|
7
|
+
keywords: ["MRR 計算器", "每月經常性收入計算器", "MRR 公式", "如何計算 MRR", "MRR 轉 ARR", "SaaS MRR 計算器"],
|
|
8
|
+
inputSignal: "客戶與計費資料,或公開流量與方案價格",
|
|
9
|
+
outputSignal: "MRR、ARR、每月 ARPA、成長率或公開資料估算",
|
|
10
|
+
boundarySignal: "瀏覽器本機計算;估算結果僅供方向判斷,並非公司揭露收入",
|
|
11
|
+
},
|
|
12
|
+
guide: {
|
|
13
|
+
workspaceHeading: "SaaS MRR 計算器",
|
|
14
|
+
workspaceHint: "用已知客戶資料計算每月經常性收入,或切換到估算模式,用公開流量和方案價格粗略判斷 SaaS MRR。",
|
|
15
|
+
standard: {
|
|
16
|
+
benefits: { title: "這個 MRR 計算器會回傳什麼", cards: [
|
|
17
|
+
{ title: "同時得到 MRR 和 ARR", body: "無需製作試算表,直接查看每月經常性收入、年度經常性收入和每月 ARPA。" },
|
|
18
|
+
{ title: "自動統一計費週期", body: "輸入月繳、季繳或年繳價格,計算器會先統一換算到每月口徑。" },
|
|
19
|
+
{ title: "也能粗略評估競品", body: "不知道客戶數時,用公開流量、方案價格和可調整假設得到明確標註的估算結果。" },
|
|
20
|
+
] },
|
|
21
|
+
imageTextSections: [
|
|
22
|
+
{ id: "choose-the-right-mrr-method", title: "選擇正確的\nMRR 計算方式", body: [{ text: "想計算自己產品的 MRR 或 ARR 時,使用預設的「計算 MRR」。填寫活躍付費客戶數、每位客戶的經常性金額和計費週期,工具會直接計算結果。", highlights: ["計算 MRR"] }, { text: "想粗略了解競品或其他 SaaS 的收入量級時,切換到「估算 SaaS MRR」。根據公開網站流量和方案價格填寫估算條件;結果只用於方向判斷,不是對方揭露的收入。", highlights: ["估算 SaaS MRR"] }], image: { src: "/tools/mrr-calculator-section-steps-8bit.webp", alt: "像素風工作台比較用客戶資料計算 MRR 與用公開流量和方案價格估算 SaaS MRR。" }, imageFirst: true, hideImageOnMobile: true },
|
|
23
|
+
{ id: "monthly-recurring-revenue-formula", title: "如何計算\n每月經常性收入", body: [{ text: "每月經常性收入公式是:MRR = 活躍付費客戶數 × 每月 ARPA。季繳經常性金額先除以 3,年繳金額先除以 12,再乘以客戶數。", highlights: ["MRR = 活躍付費客戶數 × 每月 ARPA"] }, "例如,12 位客戶每年支付 1,200 美元,每月 ARPA 為 100 美元,MRR 為 1,200 美元,ARR 為 14,400 美元。若上月 MRR 為 1,000 美元,月增率是(1,200 − 1,000)÷ 1,000 = 20%。"], image: { src: "/tools/mrr-calculator-section-primary-8bit.webp", alt: "像素風 SaaS 帳本把月繳、季繳和年繳訂閱統一換算成 MRR。" } },
|
|
24
|
+
{ id: "estimate-saas-mrr", title: "用公開資料\n估算 SaaS MRR", body: ["估算模式先用「造訪量 × 可用於獲客的造訪比例 × 訪客轉付費率」估算每月新增付費客戶,再用平均付費週期估算活躍客戶數,最後乘以換算後的每月方案價格。", "重複造訪、現有客戶、獲客通路、轉換率、方案結構和流失仍然未知。比較多個產品時應保持假設一致,商業模式不同時再調整;結果只適合方向判斷。"], image: { src: "/tools/mrr-calculator-section-variation-8bit.webp", alt: "像素風公開流量圖和方案價格卡片進入明確標註的 SaaS MRR 估算結果。" }, imageFirst: true },
|
|
25
|
+
],
|
|
26
|
+
editorialSections: [
|
|
27
|
+
{ id: "what-counts-as-mrr", title: "哪些收入應計入每月經常性收入", body: ["MRR 是統一到單月口徑的經常性訂閱收入。應計入目前合約下持續發生的有效方案收入,季繳或年繳金額需要先換算到每月。", "不要計入一次性設定費、導入費、專業服務、硬體、稅費和其他非經常性收款。如果涉及折扣、退款、用量計費、擴張、收縮或流失,應使用帳務系統資料和更完整的收入模型。"] },
|
|
28
|
+
{ id: "common-mrr-calculation-mistakes", title: "常見的 MRR 計算錯誤", body: ["不要把整筆年繳收入全部放進一個月,也不要計入一次性收入,或把新增 MRR 當成公司總 MRR。涉及多個方案時,應分別計算後相加,或使用加權平均月價,不要假設客戶平均分布。", "在估算模式中,不要把所有網站造訪量或直接流量都當成新客戶。流量可能包含回訪者、現有客戶、員工和重複工作階段,因此可用於獲客的造訪比例必須保持為明確假設。", "最重要的是,不要把 Estimated MRR 情境寫成競品真實收入。公開流量和價格無法揭示真實客戶數、折扣、留存和獲客通路。"] },
|
|
29
|
+
{ id: "use-mrr-and-arr-results", title: "如何使用 MRR 和 ARR 結果", body: ["核對客戶數、經常性金額和計費週期後,可以把計算 MRR 用於營運檢查、每月報告或方案情境測試。填寫上月 MRR 可以查看月增率,把 MRR 轉成 ARR 可以得到年化經常性收入視角。", "估算 MRR 只適合競品方向性比較。比較不同產品時保持同一組假設,記錄結果依據,並在流量或價格變化後重新計算。", "這些結果可以繼續用於流失率、留存和 LTV:CAC 分析,但不屬於會計報表、財務建議或估值意見。"] },
|
|
30
|
+
],
|
|
31
|
+
faqIntro: "補充回答多方案、零分母、直接流量、估算邊界和瀏覽器本機資料處理問題。", faqCtaLabel: "使用計算器",
|
|
32
|
+
},
|
|
33
|
+
faqHeading: "MRR 計算器常見問題",
|
|
34
|
+
faqItems: [
|
|
35
|
+
{ question: "這個 MRR 計算器如何使用?", answer: "想計算自己產品的 MRR 或 ARR 時,保留預設的「計算 MRR」,填寫活躍付費客戶數、每位客戶的經常性金額和計費週期。想粗略了解競品或其他 SaaS 的收入量級時,切換到「估算 SaaS MRR」,根據公開網站流量和方案價格填寫估算條件。前者是依你的輸入直接計算;後者只是方向性估算。" },
|
|
36
|
+
{ question: "不同方案可以一起計算嗎?", answer: "V1 接收一個混合平均經常性金額。你可以分別計算各方案後相加,或在知道方案比例時先算出加權平均月價。" },
|
|
37
|
+
{ question: "上月 MRR 為零時怎麼辦?", answer: "工具仍會回傳本月 MRR 和 ARR,但不會顯示月增率,因為分母為零時百分比成長沒有可靠含義。" },
|
|
38
|
+
{ question: "可以把直接流量當成新客戶嗎?", answer: "不可以。直接流量可能包含回訪者、現有客戶、員工、書籤造訪和無法歸因的工作階段。估算時只應計入一部分可用於獲客的造訪量。" },
|
|
39
|
+
{ question: "SaaS MRR 估算有多準確?", answer: "它只適合方向判斷,不是精確結果。是否有參考價值取決於流量、價格、轉換率和付費週期假設是否接近被評估產品。" },
|
|
40
|
+
{ question: "這個估算屬於財務建議或估值意見嗎?", answer: "不屬於。它只是基於公開輸入和你的假設得到的情境,不應作為會計證據、公司收入事實、財務建議或估值結論。" },
|
|
41
|
+
{ question: "輸入的資料會離開瀏覽器嗎?", answer: "不會。目前版本在瀏覽器內完成計算,不上傳輸入值,也不需要註冊帳號。" },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
widget: {
|
|
45
|
+
calculateMode: "計算 MRR", estimateMode: "估算 SaaS MRR", modeSwitchLabel: "切換計算與估算 MRR 模式", calculateHint: "使用已知付費客戶和訂閱資料,得到計算結果。", estimateHint: "使用公開流量和價格進行粗略估算,並非公司揭露收入。",
|
|
46
|
+
activeCustomers: "活躍付費客戶數", recurringAmount: "每位客戶的經常性金額", billingPeriod: "計費週期", previousMrr: "上月 MRR", optional: "選填", addPreviousMrr: "加入上月 MRR(選填)", currency: "幣別", monthlyVisits: "估算的網站每月造訪量", planPrice: "有代表性的公開方案價格", showAssumptions: "調整估算假設", hideAssumptions: "收起估算假設", assumptionNote: "這些只是起始假設,不是產業基準。請根據被評估產品進行調整。", eligibleVisitShare: "可用於獲客的造訪比例(%)", visitorToPaidRate: "訪客轉付費率(%)", averagePaidLifetime: "平均付費週期(月)",
|
|
47
|
+
periods: { month: "月繳", quarter: "季繳", year: "年繳" }, calculatedLabel: "計算結果", estimatedLabel: "估算結果", calculatedResultTitle: "每月經常性收入", estimatedResultTitle: "SaaS 收入方向性估算", calculatedMrr: "計算 MRR", estimatedMrr: "估算 MRR", arr: "ARR", estimatedArr: "估算 ARR", monthlyArpa: "每月 ARPA", monthlyPlanPrice: "每月方案價格", momGrowth: "MRR 月增率", newPaidCustomers: "每月新增付費客戶", activePaidCustomers: "估算活躍付費客戶", formula: "算式檢查", perMonth: "每月", assumptionsUsed: "本次使用的假設", notAvailable: "無法計算",
|
|
48
|
+
calculatedGuidance: "只計入經常性訂閱收入,排除一次性費用、稅費和非經常性服務。", estimateDisclaimer: "僅為公開資料估算。實際流量品質、轉換率、方案結構、流失和收入可能不同。", copyResult: "複製結果", copied: "已複製", reset: "重設", fixInputsTitle: "檢查標紅的輸入項", fixInputsBody: "輸入有效數值後,結果會自動更新。", errors: { nonNegative: "請輸入零或正數。", positive: "請輸入大於零的數值。", percent: "請輸入 0 到 100 之間的百分比。" },
|
|
49
|
+
},
|
|
50
|
+
} as const;
|
|
51
|
+
|
|
52
|
+
export default mrrCalculator;
|
|
@@ -492,8 +492,9 @@ const toolsUi = {
|
|
|
492
492
|
downloadZip: "下載 ZIP",
|
|
493
493
|
selectedTitle: "選中圖標",
|
|
494
494
|
downloadPng: "下載 PNG",
|
|
495
|
-
outputEmptyTitle: "
|
|
496
|
-
outputEmptyHint: "
|
|
495
|
+
outputEmptyTitle: "上傳後在這裡檢查檔案包",
|
|
496
|
+
outputEmptyHint: "目前的平台選擇會生成下列主要目錄。上傳源圖並生成後,這裡會切換為實際 PNG 預覽和 ZIP 下載。",
|
|
497
|
+
outputExampleLabel: "app-icon-assets.zip 範例結構",
|
|
497
498
|
platforms: {
|
|
498
499
|
all: "App Store + Google Play",
|
|
499
500
|
apple: "Apple App Store / iOS",
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from "../../i18n/locale-fallback";
|
|
6
6
|
import type { AppLocale } from "../../i18n/locale";
|
|
7
7
|
import enToolsPages from "../../i18n/messages/en/tools";
|
|
8
|
+
import zhTwToolsPages from "../../i18n/messages/zh-tw/tools";
|
|
8
9
|
import zhToolsPages from "../../i18n/messages/zh/tools";
|
|
9
10
|
import type { ToolLocaleCopy, ToolSpec } from "../../features/tools/types";
|
|
10
11
|
|
|
@@ -13,6 +14,10 @@ import { resolveToolsPagesKey } from "./tool-message-keys";
|
|
|
13
14
|
type ToolPageModule = { meta?: ToolLocaleCopy };
|
|
14
15
|
|
|
15
16
|
function toolsPagesForPrimary(locale: AppLocale): Record<string, ToolPageModule> {
|
|
17
|
+
if (locale === "zh-tw") {
|
|
18
|
+
return zhTwToolsPages as unknown as Record<string, ToolPageModule>;
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
return (resolvePrimaryLocale(locale) === "zh"
|
|
17
22
|
? zhToolsPages
|
|
18
23
|
: enToolsPages) as unknown as Record<string, ToolPageModule>;
|
|
@@ -62,12 +67,13 @@ export function resolveToolSpecLocales(spec: ToolSpec): PartialLocaleRecord<Tool
|
|
|
62
67
|
|
|
63
68
|
const zh = getToolI18nMeta(spec.slug, "zh") ?? spec.locales?.zh;
|
|
64
69
|
const en = getToolI18nMeta(spec.slug, "en") ?? spec.locales?.en;
|
|
70
|
+
const zhTw = getToolI18nMeta(spec.slug, "zh-tw") ?? spec.locales?.["zh-tw"];
|
|
65
71
|
|
|
66
72
|
if (!zh || !en) {
|
|
67
73
|
throw new Error(`Missing zh/en locale copy for tool "${spec.slug}"`);
|
|
68
74
|
}
|
|
69
75
|
|
|
70
|
-
return { zh, en };
|
|
76
|
+
return zhTw ? { zh, en, "zh-tw": zhTw } : { zh, en };
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
export function withResolvedLocales(spec: ToolSpec): ToolSpec {
|