@l10nmonster/server 3.0.0-alpha.3 → 3.0.0-alpha.5

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/README.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  L10n Monster Manager UI - Web-based interface for managing localization projects and translation workflows.
4
4
 
5
+ ## How It Works
6
+
7
+ The L10n Monster server is implemented as an **action** in the L10n Monster CLI. When you run `l10n serve --ui`, it:
8
+
9
+ 1. **Starts an Express server** that connects to the current L10n Monster project
10
+ 2. **Serves real-time data** from your project's translation memory and source files
11
+ 3. **Optionally serves the web UI** (when `--ui` flag is used) from pre-built static files
12
+
13
+ ### Important: This is NOT a standalone server
14
+ - The server must be run from within a L10n Monster project directory (where `l10nmonster.config.mjs` exists)
15
+ - It accesses the project's MonsterManager instance (`mm`) to fetch real translation data
16
+ - The API endpoints return actual project data, not mock data
17
+
5
18
  ## Installation
6
19
 
7
20
  ```bash
@@ -10,76 +23,128 @@ npm install @l10nmonster/server
10
23
 
11
24
  ## Usage
12
25
 
13
- Start the server with API only:
26
+ ### From within a L10n Monster project:
14
27
 
15
28
  ```bash
16
- l10n serve --port 9691
29
+ # Navigate to your l10nmonster project directory
30
+ cd samples/CardboardSDK/l10nmonster
31
+
32
+ # Start the server with API only
33
+ npx l10n serve --port 9691
34
+
35
+ # Start the server with web UI
36
+ npx l10n serve --port 9691 --ui
17
37
  ```
18
38
 
19
- Start the server with web UI:
39
+ ## Building the UI
40
+
41
+ The UI must be built before it can be served:
20
42
 
21
43
  ```bash
22
- l10n serve --port 9691 --ui
44
+ cd server
45
+ npm install
46
+ npm run build
23
47
  ```
24
48
 
49
+ This creates the `ui/dist` directory with the built static files.
50
+
25
51
  ## Features
26
52
 
27
53
  ### Web UI
28
54
 
29
55
  Modern React-based interface built with:
30
- - **Material-UI**: Professional design system
56
+ - **Chakra UI 3.0**: Modern component library with excellent performance
57
+ - **React 19**: Latest React with improved performance
58
+ - **TypeScript**: Type-safe development
31
59
  - **React Router**: Client-side routing
32
60
  - **Vite**: Fast development and build tooling
33
61
  - **Responsive Design**: Works on desktop and mobile
34
62
 
35
63
  ### API Endpoints
36
64
 
37
- - `GET /api/status` - Translation status and project overview
38
- - `GET /api/untranslated/:sourceLang/:targetLang` - Untranslated content for language pairs
39
- - `GET /api/tm/stats/:sourceLang/:targetLang` - Translation memory statistics
65
+ All endpoints return **real project data** from the MonsterManager instance:
66
+
67
+ - `GET /api/status` - Real-time translation status from `mm.getTranslationStatus()`
68
+ - `GET /api/untranslated/:sourceLang/:targetLang` - Actual untranslated content (currently using mock data, needs implementation)
69
+ - `GET /api/tm/stats/:sourceLang/:targetLang` - Translation memory statistics (currently using mock data, needs implementation)
40
70
 
41
71
  ### Pages
42
72
 
43
- - **Home**: Project overview and status dashboard
73
+ - **Home**: Project overview with real translation status
44
74
  - **Untranslated**: Browse untranslated content by language pair
45
- - **Translation Memory**: View and manage translation memory statistics
75
+ - **Translation Memory**: View translation memory statistics
46
76
  - **404**: Error handling for unknown routes
47
77
 
48
78
  ## Development
49
79
 
50
- ### Start Development Server
80
+ ### Prerequisites
51
81
 
82
+ 1. Build the UI first:
52
83
  ```bash
53
- npm run dev
84
+ cd server
85
+ npm install
86
+ npm run build
54
87
  ```
55
88
 
89
+ 2. Run from a L10n Monster project:
90
+ ```bash
91
+ cd your-project/l10nmonster
92
+ npx l10n serve --ui
93
+ ```
94
+
95
+ ### Development Mode
96
+
97
+ For UI development with hot reload:
98
+
99
+ ```bash
100
+ cd server
101
+ npm run dev # Runs Vite dev server on port 5173
102
+ ```
103
+
104
+ Note: In development mode, the Vite dev server proxies API calls to `http://localhost:9691`, so you need the L10n Monster server running.
105
+
56
106
  ### Build for Production
57
107
 
58
108
  ```bash
109
+ cd server
59
110
  npm run build
60
111
  ```
61
112
 
62
- ### Preview Production Build
113
+ ### Run Tests
63
114
 
64
115
  ```bash
65
- npm run preview
116
+ # Frontend tests
117
+ npm run test
118
+
119
+ # Server tests
120
+ npm run test:server
121
+
122
+ # Test coverage
123
+ npm run test:coverage
66
124
  ```
67
125
 
68
126
  ## Configuration
69
127
 
70
128
  The server supports:
71
- - **Custom Port**: Specify listening port with `--port` option
129
+ - **Custom Port**: Specify listening port with `--port` option (default: 9691)
72
130
  - **UI Toggle**: Enable/disable web interface with `--ui` flag
73
131
  - **CORS**: Cross-Origin Resource Sharing enabled for API access
74
- - **Static Serving**: Production-ready static file serving
132
+ - **Static Serving**: Production-ready static file serving from `ui/dist`
75
133
 
76
134
  ## Architecture
77
135
 
78
- - **Express.js**: Backend API server
136
+ ### Backend (index.js)
137
+ - Implemented as a L10n Monster action class
138
+ - Receives MonsterManager instance (`mm`) with access to project data
139
+ - Express.js server with API routes
140
+ - Serves static UI files when `--ui` flag is used
141
+
142
+ ### Frontend (ui/)
79
143
  - **React 19**: Modern frontend framework
80
- - **Material-UI v7**: Component library and theming
144
+ - **Chakra UI 3.0**: High-performance component library
145
+ - **TypeScript**: Type safety throughout
146
+ - **Vite**: Build tool and development server
81
147
  - **Client-Side Routing**: Single-page application with React Router
82
- - **Mock Data**: Development-friendly mock data system
83
148
 
84
149
  ## Dependencies
85
150
 
@@ -89,13 +154,15 @@ The server supports:
89
154
  - `open`: Automatic browser launching
90
155
 
91
156
  ### Frontend
92
- - `react` & `react-dom`: Core React libraries
93
- - `@mui/material` & `@mui/icons-material`: Material Design components
94
- - `@emotion/react` & `@emotion/styled`: CSS-in-JS styling
157
+ - `react` & `react-dom`: Core React libraries (v19)
158
+ - `@chakra-ui/react`: Chakra UI 3.0 components
159
+ - `lucide-react`: Modern icon library
95
160
  - `react-router-dom`: Client-side routing
161
+ - `typescript`: Type-safe development
96
162
 
97
163
  ### Development
98
164
  - `vite`: Build tool and development server
99
165
  - `@vitejs/plugin-react`: React integration for Vite
100
166
  - `vitest`: Testing framework
101
- - `@testing-library/*`: React testing utilities
167
+ - `@testing-library/*`: React testing utilities
168
+ - `eslint` & `prettier`: Code quality tools
package/index.js CHANGED
@@ -2,7 +2,13 @@ import express from 'express';
2
2
  import cors from 'cors';
3
3
  import path from 'path';
4
4
  import open from 'open';
5
- import * as mockData from './mockData.js'; // Import named exports, add .js extension
5
+ import { readFileSync } from 'fs';
6
+ import { getBaseDir } from '@l10nmonster/core';
7
+ import { setupStatusRoute } from './routes/status.js';
8
+ import { setupActiveContentStatsRoute } from './routes/sources.js';
9
+ import { setupTmRoutes } from './routes/tm.js';
10
+
11
+ const serverPackage = JSON.parse(readFileSync(path.join(import.meta.dirname, 'package.json'), 'utf-8'));
6
12
 
7
13
  export default class serve {
8
14
  static help = {
@@ -24,32 +30,18 @@ export default class serve {
24
30
  // === API Routes ===
25
31
  const apiRouter = express.Router();
26
32
 
27
- // GET /api/status
28
- apiRouter.get('/status', async (req, res) => {
29
- try {
30
- const status = await mm.getTranslationStatus()
31
- res.json(status);
32
- } catch (error) {
33
- console.error('Error fetching status: ', error);
34
- res.status(500).json({ message: 'Problems fetching status data' });
35
- }
36
- });
37
-
38
- // GET /api/untranslated/:sourceLang/:targetLang
39
- apiRouter.get('/untranslated/:sourceLang/:targetLang', (req, res) => {
40
- const { sourceLang, targetLang } = req.params;
41
- const pairKey = `${sourceLang}_${targetLang}`;
42
- const content = mockData.untranslatedContent[pairKey] || [];
43
- res.json(content);
44
- });
45
-
46
- // GET /api/tm/stats/:sourceLang/:targetLang
47
- apiRouter.get('/tm/stats/:sourceLang/:targetLang', (req, res) => {
48
- const { sourceLang, targetLang } = req.params;
49
- const pairKey = `${sourceLang}_${targetLang}`;
50
- const tmInfo = mockData.tmData[pairKey] || { summary: { totalUnits: 0, lastUpdated: 'N/A' }, units: [] };
51
- res.json(tmInfo);
33
+ apiRouter.get('/info', async (req, res) => {
34
+ res.json({
35
+ version: serverPackage.version,
36
+ description: serverPackage.description,
37
+ baseDir: path.resolve(getBaseDir()),
38
+ });
52
39
  });
40
+
41
+ // Setup routes from separate files
42
+ setupStatusRoute(apiRouter, mm);
43
+ setupActiveContentStatsRoute(apiRouter, mm);
44
+ setupTmRoutes(apiRouter, mm);
53
45
 
54
46
  // Mount the API router under the /api prefix
55
47
  app.use('/api', apiRouter);
package/package.json CHANGED
@@ -1,22 +1,21 @@
1
1
  {
2
2
  "name": "@l10nmonster/server",
3
- "version": "3.0.0-alpha.3",
4
- "description": "L10n Monster Manager UI",
3
+ "version": "3.0.0-alpha.5",
4
+ "description": "L10n Monster Manager",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "dependencies": {
8
+ "@chakra-ui/react": "^3.2.0",
8
9
  "@emotion/react": "^11.14.0",
9
- "@emotion/styled": "^11.14.0",
10
- "@mui/icons-material": "^7.0.2",
11
- "@mui/material": "^7.0.2",
12
- "@testing-library/dom": "^10.4.0",
13
- "@testing-library/user-event": "^14",
14
10
  "cors": "^2.8.5",
15
11
  "dotenv": "^17",
16
12
  "express": "^5.1.0",
13
+ "lucide-react": "^0.525.0",
14
+ "next-themes": "^0.4.6",
17
15
  "open": "^10.1.1",
18
16
  "react": "^19.1.0",
19
17
  "react-dom": "^19.1.0",
18
+ "react-icons": "^5.5.0",
20
19
  "react-router-dom": "^7.5.2",
21
20
  "web-vitals": "^5"
22
21
  },
@@ -24,7 +23,15 @@
24
23
  "dev": "vite",
25
24
  "build": "vite build",
26
25
  "preview": "vite preview",
27
- "test": "node --test"
26
+ "test": "vitest run",
27
+ "test:watch": "vitest",
28
+ "test:ui": "vitest --ui",
29
+ "test:coverage": "vitest run --coverage",
30
+ "test:server": "node --test test/*.js",
31
+ "lint": "eslint .",
32
+ "lint:fix": "eslint . --fix",
33
+ "format": "prettier --write \"ui/src/**/*.{js,jsx,ts,tsx,json,css,md}\"",
34
+ "type-check": "tsc --noEmit"
28
35
  },
29
36
  "eslintConfig": {
30
37
  "extends": [
@@ -45,11 +52,28 @@
45
52
  ]
46
53
  },
47
54
  "devDependencies": {
55
+ "@testing-library/dom": "^10.4.0",
48
56
  "@testing-library/jest-dom": "^6.6.3",
49
57
  "@testing-library/react": "^16.3.0",
58
+ "@testing-library/user-event": "^14.5.2",
59
+ "@types/react": "^19.0.6",
60
+ "@types/react-dom": "^19.0.2",
61
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
62
+ "@typescript-eslint/parser": "^8.0.0",
50
63
  "@vitejs/plugin-react": "^4.4.1",
64
+ "@vitest/coverage-v8": "^3.1.2",
65
+ "@vitest/ui": "^3.1.2",
66
+ "eslint": "^9",
67
+ "eslint-config-prettier": "^10",
68
+ "eslint-plugin-prettier": "^5.0.0",
69
+ "eslint-plugin-react": "^7.35.0",
70
+ "eslint-plugin-react-hooks": "^5",
51
71
  "jsdom": "^26.1.0",
72
+ "prettier": "^3.5.0",
73
+ "supertest": "^7.0.0",
74
+ "typescript": "^5.7.3",
52
75
  "vite": "^7",
76
+ "vite-tsconfig-paths": "^5.1.4",
53
77
  "vitest": "^3.1.2"
54
78
  },
55
79
  "engines": {
@@ -0,0 +1,10 @@
1
+ export function setupActiveContentStatsRoute(router, mm) {
2
+ router.get('/activeContentStats', async (req, res) => {
3
+ const sources = {};
4
+ for (const channelId of Object.keys(mm.rm.channels)) {
5
+ const channelStats = await mm.rm.getActiveContentStats(channelId);
6
+ sources[channelId] = channelStats;
7
+ }
8
+ res.json(sources);
9
+ });
10
+ }
@@ -0,0 +1,34 @@
1
+ export function setupStatusRoute(router, mm) {
2
+ router.get('/status', async (req, res) => {
3
+ try {
4
+ const status = await mm.getTranslationStatus();
5
+
6
+ // Transform the structure from:
7
+ // source_lang -> target_lang -> channel -> project -> data
8
+ // to:
9
+ // channel -> project -> source_lang -> target_lang -> data
10
+ const flippedStatus = {};
11
+
12
+ for (const [sourceLang, targetLangs] of Object.entries(status)) {
13
+ for (const [targetLang, channels] of Object.entries(targetLangs)) {
14
+ for (const [channelId, projects] of Object.entries(channels)) {
15
+ for (const [projectName, data] of Object.entries(projects)) {
16
+ // Initialize nested structure if it doesn't exist
17
+ flippedStatus[channelId] ??= {};
18
+ flippedStatus[channelId][projectName] ??= {};
19
+ flippedStatus[channelId][projectName][sourceLang] ??= {};
20
+
21
+ // Set the data at the new location
22
+ flippedStatus[channelId][projectName][sourceLang][targetLang] = data;
23
+ }
24
+ }
25
+ }
26
+ }
27
+
28
+ res.json(flippedStatus);
29
+ } catch (error) {
30
+ console.error('Error fetching status: ', error);
31
+ res.status(500).json({ message: 'Problems fetching status data' });
32
+ }
33
+ });
34
+ }
package/routes/tm.js ADDED
@@ -0,0 +1,12 @@
1
+ export function setupTmRoutes(router, mm) {
2
+ router.get('/tm/stats', async (req, res) => {
3
+ const tmInfo = {};
4
+ const availableLangPairs = (await mm.tmm.getAvailableLangPairs()).sort();
5
+ for (const [sourceLang, targetLang] of availableLangPairs) {
6
+ const tm = mm.tmm.getTM(sourceLang, targetLang);
7
+ tmInfo[sourceLang] ??= {};
8
+ tmInfo[sourceLang][targetLang] = tm.getStats();
9
+ }
10
+ res.json(tmInfo);
11
+ });
12
+ }
@@ -0,0 +1 @@
1
+ import{r as i,j as e,C as d,S as b,B as s,T as t,F as y}from"./index-Ce8PP-0Z.js";import{A as z,f as C}from"./api-DXOYnFyU.js";import{B as h}from"./badge-CveKztw5.js";import{G as w}from"./grid-DetiGbYY.js";import{V as x}from"./v-stack-CQ6LIfdw.js";const E=()=>{const[f,j]=i.useState({}),[m,u]=i.useState(!0),[g,p]=i.useState(null);i.useEffect(()=>{(async()=>{try{const n=await C("/api/activeContentStats");j(n)}catch(n){console.error("Error fetching content stats:",n),p(n instanceof Error?n.message:"Failed to fetch content statistics")}finally{u(!1)}})()},[]);const S=a=>{const n=new Date,o=new Date(a),c=n-o,r=Math.floor(c/1e3),l=new Intl.RelativeTimeFormat("en",{numeric:"auto",style:"short"});return r<60?l.format(-r,"second"):r<3600?l.format(-Math.floor(r/60),"minute"):r<86400?l.format(-Math.floor(r/3600),"hour"):r<2592e3?l.format(-Math.floor(r/86400),"day"):r<31536e3?l.format(-Math.floor(r/2592e3),"month"):l.format(-Math.floor(r/31536e3),"year")};return m?e.jsx(d,{display:"flex",justifyContent:"center",mt:10,children:e.jsx(b,{size:"xl"})}):g?e.jsx(d,{mt:5,children:e.jsx(z,{status:"error",children:e.jsxs(s,{children:[e.jsx(t,{fontWeight:"bold",children:"Error"}),e.jsx(t,{children:g})]})})}):e.jsx(d,{maxWidth:"6xl",py:6,children:e.jsxs(x,{gap:6,align:"stretch",children:[e.jsxs(s,{children:[e.jsx(t,{fontSize:"2xl",fontWeight:"bold",mb:2,children:"Active Content Sources"}),e.jsx(t,{color:"gray.600",children:"Overview of content sources across all channels"})]}),Object.keys(f).length===0?e.jsx(s,{p:6,borderWidth:"1px",borderRadius:"md",bg:"white",textAlign:"center",children:e.jsx(t,{color:"gray.500",children:"No active content found."})}):e.jsx(x,{gap:6,align:"stretch",children:Object.entries(f).map(([a,n])=>e.jsx(s,{p:6,borderWidth:"1px",borderRadius:"lg",bg:"white",shadow:"sm",children:e.jsxs(x,{gap:4,align:"stretch",children:[e.jsx(s,{children:e.jsx(t,{fontSize:"xl",fontWeight:"bold",color:"blue.600",mb:2,children:a})}),n.map((o,c)=>e.jsx(s,{p:3,borderWidth:"1px",borderRadius:"md",borderColor:"gray.200",bg:"gray.50",children:e.jsxs(w,{templateColumns:"1fr 80px 1fr 80px 80px 100px",gap:4,alignItems:"center",children:[e.jsxs(s,{children:[e.jsx(t,{fontSize:"xs",color:"gray.500",mb:1,children:"Project"}),e.jsx(t,{fontSize:"sm",fontWeight:"semibold",children:o.prj||"Default"})]}),e.jsxs(s,{children:[e.jsx(t,{fontSize:"xs",color:"gray.500",mb:1,children:"Source"}),e.jsx(h,{colorPalette:"blue",size:"sm",children:o.sourceLang})]}),e.jsxs(s,{children:[e.jsxs(t,{fontSize:"xs",color:"gray.500",mb:1,children:["Targets (",o.targetLangs.length,")"]}),e.jsxs(y,{wrap:"wrap",gap:1,children:[o.targetLangs.slice(0,3).map(r=>e.jsx(h,{colorPalette:"green",size:"sm",children:r},r)),o.targetLangs.length>3&&e.jsxs(h,{colorPalette:"gray",size:"sm",children:["+",o.targetLangs.length-3]})]})]}),e.jsxs(s,{textAlign:"center",children:[e.jsx(t,{fontSize:"xs",color:"gray.500",mb:1,children:"Resources"}),e.jsx(t,{fontSize:"sm",fontWeight:"bold",color:"orange.600",children:o.resCount.toLocaleString()})]}),e.jsxs(s,{textAlign:"center",children:[e.jsx(t,{fontSize:"xs",color:"gray.500",mb:1,children:"Segments"}),e.jsx(t,{fontSize:"sm",fontWeight:"bold",color:"purple.600",children:o.segmentCount.toLocaleString()})]}),e.jsxs(s,{textAlign:"center",children:[e.jsx(t,{fontSize:"xs",color:"gray.500",mb:1,children:"Modified"}),e.jsx(t,{fontSize:"xs",color:"gray.600",children:S(o.lastModified)})]})]})},c))]})},a))})]})})};export{E as default};
@@ -0,0 +1 @@
1
+ import{c as z,j as t,T as e,B as n,F as W,r as f,C as y,S as v}from"./index-Ce8PP-0Z.js";import{A as $,f as E}from"./api-DXOYnFyU.js";import{G as O}from"./grid-DetiGbYY.js";const{withProvider:R,withContext:d}=z({key:"card"}),D=R("div","root"),L=d("div","body");d("div","header");d("div","footer");d("h3","title");d("p","description");const T=({project:h})=>{const{sourceLang:m,targetLang:x,resCount:g,segmentCount:o,translationStatus:j}=h,s={untranslated:0,"in flight":0,translated:0,"low quality":0,words:0,chars:0};for(const{minQ:i,q:a,seg:l,words:p,chars:u}of j){const c=a===null?"untranslated":a===0?"in flight":a>=i?"translated":"low quality";s[c]+=l,s.words+=p,s.chars+=u}const r=Math.round(s.translated/o*100);return t.jsx(D,{variant:"outline",children:t.jsxs(L,{children:[t.jsxs(e,{fontSize:"lg",fontWeight:"semibold",mb:2,children:[m," → ",x]}),t.jsx(n,{mb:3,children:t.jsxs(W,{align:"center",gap:2,mb:1,children:[t.jsx(n,{flex:"1",bg:"gray.200",rounded:"full",height:"6px",position:"relative",children:t.jsx(n,{bg:"blue.500",height:"100%",rounded:"full",width:`${r}%`,transition:"width 0.3s ease"})}),t.jsxs(e,{fontSize:"sm",color:"gray.600",minW:"45px",children:[r,"%"]})]})}),t.jsxs(e,{fontSize:"sm",children:["Resources: ",g.toLocaleString()]}),t.jsxs(e,{fontSize:"sm",children:["Segments: ",o.toLocaleString()]}),t.jsxs(e,{fontSize:"sm",children:["Words: ",s.words.toLocaleString()]}),s.untranslated>0&&t.jsxs(e,{fontSize:"sm",color:"orange.600",children:["Untranslated segments: ",s.untranslated.toLocaleString()]})]})})},P=()=>{const[h,m]=f.useState({}),[x,g]=f.useState(!0),[o,j]=f.useState(null);return f.useEffect(()=>{(async()=>{try{const r=await E("/api/status");m(r)}catch(r){console.error("Error fetching status:",r),j(r instanceof Error?r.message:"Failed to fetch status data")}finally{g(!1)}})()},[]),x?t.jsx(y,{display:"flex",justifyContent:"center",mt:5,children:t.jsx(v,{size:"xl"})}):o?t.jsx(y,{mt:5,children:t.jsx($,{status:"error",children:t.jsxs(n,{children:[t.jsx(e,{fontWeight:"bold",children:"Error"}),t.jsx(e,{children:o})]})})}):t.jsxs(y,{maxWidth:"6xl",py:6,children:[Object.entries(h).map(([s,r])=>Object.entries(r).map(([i,a])=>Object.entries(a).map(([l,p])=>t.jsxs(n,{mb:6,p:3,borderWidth:"1px",borderRadius:"md",bg:"white",borderColor:"gray.200",children:[t.jsxs(n,{display:"flex",alignItems:"center",gap:3,flexWrap:"wrap",children:[t.jsxs(n,{children:[t.jsx(e,{fontSize:"sm",color:"gray.600",mb:1,children:"Channel"}),t.jsx(e,{fontSize:"xl",fontWeight:"semibold",color:"blue.600",children:s})]}),t.jsx(n,{height:"40px",width:"1px",bg:"gray.300"}),t.jsxs(n,{children:[t.jsx(e,{fontSize:"sm",color:"gray.600",mb:1,children:"Project"}),t.jsx(e,{fontSize:"xl",fontWeight:"semibold",color:"green.600",children:i})]})]}),t.jsx(O,{templateColumns:"repeat(auto-fit, minmax(300px, 1fr))",gap:4,mt:4,children:Object.entries(p).map(([u,c])=>{const C=c.reduce((b,S)=>b+S.res,0),w=c.reduce((b,S)=>b+S.seg,0);return t.jsx(T,{project:{sourceLang:l,targetLang:u,resCount:C,segmentCount:w,translationStatus:c}},`${l}-${u}-${i}-${s}`)})})]},`${l}-${s}-${i}`)))),Object.keys(h).length===0&&!x&&t.jsx(e,{mt:4,color:"gray.600",children:"No active content found."})]})};export{P as default};
@@ -0,0 +1 @@
1
+ import{r as i,j as e,C as a,S,B as r,T as t,F as y}from"./index-Ce8PP-0Z.js";import{A as z,f as W}from"./api-DXOYnFyU.js";import{B as c}from"./badge-CveKztw5.js";import{G as T}from"./grid-DetiGbYY.js";import{V as l}from"./v-stack-CQ6LIfdw.js";const B=()=>{const[d,g]=i.useState({}),[j,m]=i.useState(!0),[x,f]=i.useState(null);i.useEffect(()=>{(async()=>{try{const s=await W("/api/tm/stats");g(s)}catch(s){console.error("Error fetching TM stats:",s),f(s instanceof Error?s.message:"Failed to fetch translation memory statistics")}finally{m(!1)}})()},[]);const u=o=>{switch(o){case"done":return"green";case"pending":return"yellow";case"error":return"red";default:return"gray"}};return j?e.jsx(a,{display:"flex",justifyContent:"center",mt:10,children:e.jsx(S,{size:"xl"})}):x?e.jsx(a,{mt:5,children:e.jsx(z,{status:"error",children:e.jsxs(r,{children:[e.jsx(t,{fontWeight:"bold",children:"Error"}),e.jsx(t,{children:x})]})})}):e.jsx(a,{maxWidth:"6xl",py:6,children:e.jsxs(l,{gap:6,align:"stretch",children:[e.jsxs(r,{children:[e.jsx(t,{fontSize:"2xl",fontWeight:"bold",mb:2,children:"Translation Memory Statistics"}),e.jsx(t,{color:"gray.600",children:"Overview of jobs by translation provider for each language pair"})]}),Object.keys(d).length===0?e.jsx(r,{p:6,borderWidth:"1px",borderRadius:"md",bg:"white",textAlign:"center",children:e.jsx(t,{color:"gray.500",children:"No translation memory data found."})}):e.jsx(l,{gap:6,align:"stretch",children:Object.entries(d).flatMap(([o,s])=>Object.entries(s).map(([h,p])=>e.jsx(r,{p:6,borderWidth:"1px",borderRadius:"lg",bg:"white",shadow:"sm",children:e.jsxs(l,{gap:4,align:"stretch",children:[e.jsx(r,{children:e.jsxs(y,{align:"center",gap:3,mb:2,children:[e.jsx(c,{colorPalette:"blue",size:"sm",children:o}),e.jsx(t,{color:"gray.500",fontSize:"lg",children:"→"}),e.jsx(c,{colorPalette:"green",size:"sm",children:h})]})}),e.jsx(l,{gap:3,align:"stretch",children:p.map((n,b)=>e.jsx(r,{p:3,borderWidth:"1px",borderRadius:"md",borderColor:"gray.200",bg:"gray.50",children:e.jsxs(T,{templateColumns:"2fr 80px 80px 80px 80px",gap:4,alignItems:"center",children:[e.jsxs(r,{children:[e.jsx(t,{fontSize:"xs",color:"gray.500",mb:1,children:"Provider"}),e.jsx(t,{fontSize:"sm",fontWeight:"semibold",children:n.translationProvider})]}),e.jsxs(r,{textAlign:"center",children:[e.jsx(t,{fontSize:"xs",color:"gray.500",mb:1,children:"Status"}),e.jsx(c,{colorPalette:u(n.status),size:"sm",children:n.status})]}),e.jsxs(r,{textAlign:"center",children:[e.jsx(t,{fontSize:"xs",color:"gray.500",mb:1,children:"TUs"}),e.jsx(t,{fontSize:"sm",fontWeight:"bold",color:"purple.600",children:n.tuCount.toLocaleString()})]}),e.jsxs(r,{textAlign:"center",children:[e.jsx(t,{fontSize:"xs",color:"gray.500",mb:1,children:"Unique"}),e.jsx(t,{fontSize:"sm",fontWeight:"bold",color:"orange.600",children:n.distinctGuids.toLocaleString()})]}),e.jsxs(r,{textAlign:"center",children:[e.jsx(t,{fontSize:"xs",color:"gray.500",mb:1,children:"Jobs"}),e.jsx(t,{fontSize:"sm",fontWeight:"bold",color:"teal.600",children:n.jobCount.toLocaleString()})]})]})},b))})]})},`${o}-${h}`)))})]})})};export{B as default};
@@ -0,0 +1 @@
1
+ import{r as s,j as e,C as n,S as g,B as t,T as r,F as l}from"./index-Ce8PP-0Z.js";import{A as p,f as j}from"./api-DXOYnFyU.js";import{V as c}from"./v-stack-CQ6LIfdw.js";const S=()=>{const[o,d]=s.useState(null),[x,h]=s.useState(!0),[a,f]=s.useState(null);return s.useEffect(()=>{(async()=>{try{const i=await j("/api/info");d(i)}catch(i){console.error("Error fetching info:",i),f(i instanceof Error?i.message:"Failed to fetch info data")}finally{h(!1)}})()},[]),x?e.jsx(n,{display:"flex",justifyContent:"center",mt:20,children:e.jsx(g,{size:"xl"})}):a?e.jsx(n,{mt:10,children:e.jsx(p,{status:"error",children:e.jsxs(t,{children:[e.jsx(r,{fontWeight:"bold",children:"Error"}),e.jsx(r,{children:a})]})})}):e.jsx(n,{maxWidth:"2xl",py:20,children:e.jsx(c,{gap:8,align:"center",children:o&&e.jsx(t,{p:8,borderWidth:"1px",borderRadius:"xl",bg:"white",shadow:"lg",width:"500px",height:"300px",position:"relative",background:"linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)",borderColor:"gray.300",children:e.jsxs(l,{height:"100%",direction:"column",justify:"space-between",children:[e.jsxs(l,{align:"center",gap:4,children:[e.jsx("img",{src:"/logo.svg",alt:"L10n Monster",width:"60",height:"60"}),e.jsx(t,{children:e.jsx(r,{fontSize:"xl",fontWeight:"bold",color:"gray.800",lineHeight:"1.2",children:o.description})})]}),e.jsxs(c,{gap:3,align:"stretch",mt:4,children:[e.jsxs(t,{children:[e.jsx(r,{fontSize:"xs",color:"gray.500",textTransform:"uppercase",letterSpacing:"wide",mb:1,children:"Version"}),e.jsx(r,{fontSize:"lg",fontWeight:"semibold",color:"blue.600",children:o.version})]}),e.jsxs(t,{children:[e.jsx(r,{fontSize:"xs",color:"gray.500",textTransform:"uppercase",letterSpacing:"wide",mb:1,children:"Project Directory"}),e.jsx(r,{fontSize:"xs",fontFamily:"mono",color:"gray.600",wordBreak:"break-all",lineHeight:"1.3",children:o.baseDir})]})]}),e.jsx(t,{position:"absolute",top:"-10px",right:"-10px",width:"80px",height:"80px",bg:"blue.50",borderRadius:"full",opacity:"0.5",zIndex:"-1"})]})})})})};export{S as default};
@@ -0,0 +1 @@
1
+ import{j as e,b as n,c as p,r as a,e as x}from"./index-Ce8PP-0Z.js";const f=t=>e.jsx(n.svg,{stroke:"currentColor",fill:"currentColor",strokeWidth:"0",viewBox:"0 0 24 24",...t,children:e.jsx("path",{d:"M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM11.0026 16L6.75999 11.7574L8.17421 10.3431L11.0026 13.1716L16.6595 7.51472L18.0737 8.92893L11.0026 16Z"})}),l=t=>e.jsx(n.svg,{stroke:"currentColor",fill:"currentColor",strokeWidth:"0",viewBox:"0 0 24 24",...t,children:e.jsx("path",{d:"M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM11 15H13V17H11V15ZM11 7H13V13H11V7Z"})}),C=t=>e.jsx(n.svg,{viewBox:"0 0 24 24",fill:"currentColor",stroke:"currentColor",strokeWidth:"0",...t,children:e.jsx("path",{d:"M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM11 7H13V9H11V7ZM11 11H13V17H11V11Z"})}),[v,A]=x({name:"AlertStatusContext",hookName:"useAlertStatusContext",providerName:"<Alert />"}),{withProvider:j,withContext:i,useStyles:w,PropsProvider:M}=p({key:"alert"}),g=j("div","root",{forwardAsChild:!0,wrapElement(t,o){return e.jsx(v,{value:{status:o.status||"info"},children:t})}}),k=M,P=i("div","title"),Z=i("div","description"),m=i("div","content"),y={info:C,warning:l,success:f,error:l,neutral:C},H=a.forwardRef(function(o,r){const s=A(),c=w(),u=typeof s.status=="string"?y[s.status]:a.Fragment,{children:d=e.jsx(u,{}),...h}=o;return e.jsx(n.span,{ref:r,...h,css:[c.indicator,o.css],children:d})}),S=Object.freeze(Object.defineProperty({__proto__:null,Content:m,Description:Z,Indicator:H,Root:g,RootPropsProvider:k,Title:P},Symbol.toStringTag,{value:"Module"}));async function V(t,o={}){try{const r=await fetch(t,o);if(!r.ok){let s;try{s=await r.json()}catch{}const c=s?.message||`HTTP error! status: ${r.status}`;throw new Error(c)}return await r.json()}catch(r){throw console.error("API Fetch Error:",r),r}}export{S as A,V as f};
@@ -0,0 +1 @@
1
+ import{a as e}from"./index-Ce8PP-0Z.js";const{withContext:t}=e({key:"badge"}),o=t("span");export{o as B};
@@ -0,0 +1 @@
1
+ import{r as g,j as p,b as w}from"./index-Ce8PP-0Z.js";const x=g.forwardRef(function(o,r){const{templateAreas:t,column:s,row:e,autoFlow:i,autoRows:a,templateRows:l,autoColumns:n,templateColumns:d,inline:m,...u}=o;return p.jsx(w.div,{...u,ref:r,css:[{display:m?"inline-grid":"grid",gridTemplateAreas:t,gridAutoColumns:n,gridColumn:s,gridRow:e,gridAutoFlow:i,gridAutoRows:a,gridTemplateRows:l,gridTemplateColumns:d},o.css]})});export{x as G};