diff --git a/src/init/i18n/locales/BritishEnglish.json b/src/init/i18n/locales/BritishEnglish.json
index 478d513..478e08c 100644
--- a/src/init/i18n/locales/BritishEnglish.json
+++ b/src/init/i18n/locales/BritishEnglish.json
@@ -18,10 +18,12 @@
"title": "DevLab",
"description": "A collection of powerful, privacy-focused developer tools. All processing happens locally in your browser.",
"getStarted": "Get Started",
- "getStartedDescription": "Start visualising and querying your JSON data with our intuitive JSONPath-based tool.",
- "openJsonViewer": "Open JSON Viewer",
- "openBmiCalculator": "Open BMI Calculator",
+ "jsonViewerDescription": "Start visualising and querying your JSON data with our intuitive JSONPath-based tool.",
+ "jsonViewer": "JSON Viewer",
+ "bmiCalculator": "BMI Calculator",
+ "jsonGrid": "JSON Grid",
"bmiCalculatorDescription": "Calculate your Body Mass Index (BMI) to assess your weight status and health. Get instant results with personalised advice based on your BMI category.",
+ "jsonGridDescription": "Convert JSON arrays into a clean table view for quick inspection and comparison.",
"features": {
"tools": {
"title": "🛠️ Developer Tools",
@@ -48,6 +50,15 @@
"copyAsCsv": "Copy as CSV",
"error": "Error:"
},
+ "jsonGrid": {
+ "jsonInput": "JSON Input",
+ "tableResult": "Table Result",
+ "rows": "rows",
+ "exportCsv": "Export CSV",
+ "empty": "No data to display.",
+ "parseError": "Invalid JSON:",
+ "arrayOnlyError": "Please provide a JSON array."
+ },
"about": {
"title": "About DevLab",
"description": "A powerful, privacy-focused tool for debugging and visualising complex JSON data structures.",
diff --git a/src/init/i18n/locales/SimplifiedChinese.json b/src/init/i18n/locales/SimplifiedChinese.json
index 59f2009..c0dfd1b 100644
--- a/src/init/i18n/locales/SimplifiedChinese.json
+++ b/src/init/i18n/locales/SimplifiedChinese.json
@@ -18,10 +18,12 @@
"title": "DevLab",
"description": "一系列强大的、注重隐私的开发者工具集合。所有处理都在您的浏览器本地进行。",
"getStarted": "开始使用",
- "getStartedDescription": "使用我们直观的基于 JSONPath 的工具开始可视化和查询您的 JSON 数据。",
- "openJsonViewer": "打开 JSON 查看器",
- "openBmiCalculator": "打开 BMI 计算器",
+ "jsonViewerDescription": "使用我们直观的基于 JSONPath 的工具开始可视化和查询您的 JSON 数据。",
+ "jsonViewer": "JSON 查看器",
+ "bmiCalculator": "BMI 计算器",
+ "jsonGrid": "JSON Grid",
"bmiCalculatorDescription": "计算您的身体质量指数(BMI)以评估您的体重状态和健康状况。根据您的 BMI 分类获得即时结果和个性化建议。",
+ "jsonGridDescription": "将 JSON 数组转换为清晰的表格视图,便于快速查看和对比。",
"features": {
"tools": {
"title": "🛠️ 开发者工具",
@@ -48,6 +50,15 @@
"copyAsCsv": "复制为 CSV",
"error": "错误:"
},
+ "jsonGrid": {
+ "jsonInput": "JSON 输入",
+ "tableResult": "表格结果",
+ "rows": "行",
+ "exportCsv": "导出 CSV",
+ "empty": "暂无可展示的数据。",
+ "parseError": "JSON 无效:",
+ "arrayOnlyError": "请输入 JSON 数组。"
+ },
"about": {
"title": "关于 DevLab",
"description": "一个强大的、注重隐私的工具,用于调试和可视化复杂的 JSON 数据结构。",
diff --git a/src/page/home/index.tsx b/src/page/home/index.tsx
index 3fea8b5..60d7c76 100644
--- a/src/page/home/index.tsx
+++ b/src/page/home/index.tsx
@@ -12,34 +12,37 @@ export default function Home() {
{/* Page Header */}
{t("home.title")}
-
- {t("home.description")}
-
+
{t("home.description")}
- {/* Main CTA - Two columns */}
-
+
-
{t("home.getStarted")}
-
- {t("home.getStartedDescription")}
-
+
{t("home.jsonViewer")}
+
{t("home.jsonViewerDescription")}
- {t("home.openJsonViewer")}
+ {t("home.getStarted")}
-
{t("home.getStarted")}
-
- {t("home.bmiCalculatorDescription")}
-
+
{t("home.bmiCalculator")}
+
{t("home.bmiCalculatorDescription")}
- {t("home.openBmiCalculator")}
+ {t("home.getStarted")}
+
+
+
+
+
{t("home.jsonGrid")}
+
{t("home.jsonGridDescription")}
+
+ {t("home.getStarted")}
@@ -47,22 +50,22 @@ export default function Home() {
{/* Features */}
-
{t("home.features.tools.title")}
-
- {t("home.features.tools.description")}
-
+
+ {t("home.features.tools.title")}
+
+
{t("home.features.tools.description")}
-
{t("home.features.privacy.title")}
-
- {t("home.features.privacy.description")}
-
+
+ {t("home.features.privacy.title")}
+
+
{t("home.features.privacy.description")}
-
{t("home.features.free.title")}
-
- {t("home.features.free.description")}
-
+
+ {t("home.features.free.title")}
+
+
{t("home.features.free.description")}
diff --git a/src/page/json-grid/index.tsx b/src/page/json-grid/index.tsx
new file mode 100644
index 0000000..a91d456
--- /dev/null
+++ b/src/page/json-grid/index.tsx
@@ -0,0 +1,152 @@
+import { useCallback, useMemo, useState } from "react"
+import { useTranslation } from "react-i18next"
+
+type RowRecord = Record
+
+export default function JsonGrid() {
+ const { t } = useTranslation()
+
+ const initialData = [
+ { id: 1, name: "Alice", role: "Developer", active: true },
+ { id: 2, name: "Bob", role: "Designer", active: false },
+ { id: 3, name: "Charlie", role: "Product Manager", active: true },
+ ]
+
+ const [jsonInput, setJsonInput] = useState(JSON.stringify(initialData, null, 2))
+
+ const result = useMemo(() => {
+ try {
+ const parsed = JSON.parse(jsonInput)
+
+ if (!Array.isArray(parsed)) {
+ return { rows: [] as RowRecord[], columns: [] as string[], error: t("jsonTable.arrayOnlyError") }
+ }
+
+ const rows: RowRecord[] = parsed.map((item) => {
+ if (item !== null && typeof item === "object" && !Array.isArray(item)) {
+ return item as RowRecord
+ }
+ return { value: item }
+ })
+
+ const columns = Array.from(new Set(rows.flatMap((row) => Object.keys(row))))
+
+ return { rows, columns, error: null }
+ } catch (e) {
+ return {
+ rows: [] as RowRecord[],
+ columns: [] as string[],
+ error: `${t("jsonTable.parseError")} ${(e as Error).message}`,
+ }
+ }
+ }, [jsonInput, t])
+
+ const formatCell = (value: unknown): string => {
+ if (value === null || value === undefined) return ""
+ if (typeof value === "object") return JSON.stringify(value)
+ return String(value)
+ }
+
+ const exportCsv = useCallback(() => {
+ if (result.error || result.rows.length === 0 || result.columns.length === 0) return
+
+ const escapeCsv = (value: string) => {
+ if (value.includes(",") || value.includes('"') || value.includes("\n")) {
+ return `"${value.replace(/"/g, '""')}"`
+ }
+ return value
+ }
+
+ const header = result.columns.map(escapeCsv).join(",")
+ const lines = result.rows.map((row) =>
+ result.columns
+ .map((column) => escapeCsv(formatCell(row[column])))
+ .join(",")
+ )
+ const csvContent = [header, ...lines].join("\n")
+
+ const blob = new Blob([`\uFEFF${csvContent}`], { type: "text/csv;charset=utf-8;" })
+ const url = URL.createObjectURL(blob)
+ const link = document.createElement("a")
+ link.href = url
+ link.setAttribute("download", "json-table.csv")
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ URL.revokeObjectURL(url)
+ }, [result, formatCell])
+
+ return (
+
+
+
+
+ {t("jsonTable.jsonInput")}
+
+
+
+
+
+
+
+ {t("jsonTable.tableResult")}
+
+
+
+ {result.rows.length} {t("jsonTable.rows")}
+
+
+
+
+
+
+ {result.error ? (
+
+ {result.error}
+
+ ) : result.rows.length === 0 ? (
+
{t("jsonTable.empty")}
+ ) : (
+
+
+
+ {result.columns.map((column) => (
+ |
+ {column}
+ |
+ ))}
+
+
+
+ {result.rows.map((row, index) => (
+
+ {result.columns.map((column) => (
+ |
+ {formatCell(row[column])}
+ |
+ ))}
+
+ ))}
+
+
+ )}
+
+
+
+ )
+}
diff --git a/src/router/index.tsx b/src/router/index.tsx
index 45c52bd..3ef5798 100644
--- a/src/router/index.tsx
+++ b/src/router/index.tsx
@@ -31,6 +31,10 @@ const router = createBrowserRouter(
path: "json-viewer",
lazy: lazy(() => import("@/page/json-viewer")),
},
+ {
+ path: "json-grid",
+ lazy: lazy(() => import("@/page/json-grid")),
+ },
{
path: "bmi-calculator",
lazy: lazy(() => import("@/page/bmi-calculator")),