@l10nmonster/server 3.0.0-alpha.2 → 3.0.0-alpha.4
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 +90 -23
- package/index.js +18 -26
- package/package.json +37 -10
- package/routes/sources.js +10 -0
- package/routes/status.js +34 -0
- package/routes/tm.js +12 -0
- package/ui/dist/assets/Sources-D0R-Sgwf.js +1 -0
- package/ui/dist/assets/Status-XBRD-MuK.js +1 -0
- package/ui/dist/assets/TM-DZ2x6--n.js +1 -0
- package/ui/dist/assets/Welcome-p4gi31Lo.js +1 -0
- package/ui/dist/assets/api-DXOYnFyU.js +1 -0
- package/ui/dist/assets/badge-CveKztw5.js +1 -0
- package/ui/dist/assets/grid-DetiGbYY.js +1 -0
- package/ui/dist/assets/index-Ce8PP-0Z.js +76 -0
- package/ui/dist/assets/v-stack-CQ6LIfdw.js +1 -0
- package/ui/dist/favicon.ico +0 -0
- package/ui/dist/index.html +1 -1
- package/ui/dist/logo.svg +123 -0
- package/ui/dist/manifest.json +3 -8
- package/mockData.js +0 -67
- package/ui/dist/assets/index-DexglpRr.js +0 -253
- package/ui/dist/logo192.png +0 -0
- package/ui/dist/logo512.png +0 -0
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
|
-
|
|
26
|
+
### From within a L10n Monster project:
|
|
14
27
|
|
|
15
28
|
```bash
|
|
16
|
-
|
|
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
|
-
|
|
39
|
+
## Building the UI
|
|
40
|
+
|
|
41
|
+
The UI must be built before it can be served:
|
|
20
42
|
|
|
21
43
|
```bash
|
|
22
|
-
|
|
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
|
-
- **
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
- `GET /api/
|
|
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
|
|
73
|
+
- **Home**: Project overview with real translation status
|
|
44
74
|
- **Untranslated**: Browse untranslated content by language pair
|
|
45
|
-
- **Translation Memory**: View
|
|
75
|
+
- **Translation Memory**: View translation memory statistics
|
|
46
76
|
- **404**: Error handling for unknown routes
|
|
47
77
|
|
|
48
78
|
## Development
|
|
49
79
|
|
|
50
|
-
###
|
|
80
|
+
### Prerequisites
|
|
51
81
|
|
|
82
|
+
1. Build the UI first:
|
|
52
83
|
```bash
|
|
53
|
-
|
|
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
|
-
###
|
|
113
|
+
### Run Tests
|
|
63
114
|
|
|
64
115
|
```bash
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
- **
|
|
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
|
-
- `@
|
|
94
|
-
-
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
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.
|
|
4
|
-
"description": "L10n Monster Manager
|
|
3
|
+
"version": "3.0.0-alpha.4",
|
|
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
|
-
"dotenv": "^
|
|
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": "
|
|
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,31 @@
|
|
|
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",
|
|
52
|
-
"
|
|
72
|
+
"prettier": "^3.5.0",
|
|
73
|
+
"supertest": "^7.0.0",
|
|
74
|
+
"typescript": "^5.7.3",
|
|
75
|
+
"vite": "^7",
|
|
76
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
53
77
|
"vitest": "^3.1.2"
|
|
78
|
+
},
|
|
79
|
+
"engines": {
|
|
80
|
+
"node": ">=22.11.0"
|
|
54
81
|
}
|
|
55
82
|
}
|
|
@@ -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
|
+
}
|
package/routes/status.js
ADDED
|
@@ -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};
|