Jiya3177 commited on
Commit
4f1e987
·
1 Parent(s): aaff8ef

feat: add frontend internationalization

Browse files
frontend/package-lock.json CHANGED
@@ -11,12 +11,15 @@
11
  "@base-ui/react": "^1.4.1",
12
  "class-variance-authority": "^0.7.1",
13
  "clsx": "^2.1.1",
 
 
14
  "lucide-react": "^1.8.0",
15
  "next": "16.2.4",
16
  "pdfjs-dist": "^5.6.205",
17
  "react": "19.2.4",
18
  "react-dom": "19.2.4",
19
  "react-dropzone": "^15.0.0",
 
20
  "react-markdown": "^10.1.0",
21
  "react-pdf": "^10.4.1",
22
  "remark-gfm": "^4.0.1",
@@ -76,6 +79,7 @@
76
  "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
77
  "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
78
  "license": "MIT",
 
79
  "dependencies": {
80
  "@babel/code-frame": "^7.29.0",
81
  "@babel/generator": "^7.29.0",
@@ -669,6 +673,7 @@
669
  "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
670
  "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
671
  "license": "MIT",
 
672
  "engines": {
673
  "node": ">=12"
674
  },
@@ -1106,9 +1111,6 @@
1106
  "cpu": [
1107
  "arm"
1108
  ],
1109
- "libc": [
1110
- "glibc"
1111
- ],
1112
  "license": "LGPL-3.0-or-later",
1113
  "optional": true,
1114
  "os": [
@@ -1125,9 +1127,6 @@
1125
  "cpu": [
1126
  "arm64"
1127
  ],
1128
- "libc": [
1129
- "glibc"
1130
- ],
1131
  "license": "LGPL-3.0-or-later",
1132
  "optional": true,
1133
  "os": [
@@ -1144,9 +1143,6 @@
1144
  "cpu": [
1145
  "ppc64"
1146
  ],
1147
- "libc": [
1148
- "glibc"
1149
- ],
1150
  "license": "LGPL-3.0-or-later",
1151
  "optional": true,
1152
  "os": [
@@ -1163,9 +1159,6 @@
1163
  "cpu": [
1164
  "riscv64"
1165
  ],
1166
- "libc": [
1167
- "glibc"
1168
- ],
1169
  "license": "LGPL-3.0-or-later",
1170
  "optional": true,
1171
  "os": [
@@ -1182,9 +1175,6 @@
1182
  "cpu": [
1183
  "s390x"
1184
  ],
1185
- "libc": [
1186
- "glibc"
1187
- ],
1188
  "license": "LGPL-3.0-or-later",
1189
  "optional": true,
1190
  "os": [
@@ -1201,9 +1191,6 @@
1201
  "cpu": [
1202
  "x64"
1203
  ],
1204
- "libc": [
1205
- "glibc"
1206
- ],
1207
  "license": "LGPL-3.0-or-later",
1208
  "optional": true,
1209
  "os": [
@@ -1220,9 +1207,6 @@
1220
  "cpu": [
1221
  "arm64"
1222
  ],
1223
- "libc": [
1224
- "musl"
1225
- ],
1226
  "license": "LGPL-3.0-or-later",
1227
  "optional": true,
1228
  "os": [
@@ -1239,9 +1223,6 @@
1239
  "cpu": [
1240
  "x64"
1241
  ],
1242
- "libc": [
1243
- "musl"
1244
- ],
1245
  "license": "LGPL-3.0-or-later",
1246
  "optional": true,
1247
  "os": [
@@ -1258,9 +1239,6 @@
1258
  "cpu": [
1259
  "arm"
1260
  ],
1261
- "libc": [
1262
- "glibc"
1263
- ],
1264
  "license": "Apache-2.0",
1265
  "optional": true,
1266
  "os": [
@@ -1283,9 +1261,6 @@
1283
  "cpu": [
1284
  "arm64"
1285
  ],
1286
- "libc": [
1287
- "glibc"
1288
- ],
1289
  "license": "Apache-2.0",
1290
  "optional": true,
1291
  "os": [
@@ -1308,9 +1283,6 @@
1308
  "cpu": [
1309
  "ppc64"
1310
  ],
1311
- "libc": [
1312
- "glibc"
1313
- ],
1314
  "license": "Apache-2.0",
1315
  "optional": true,
1316
  "os": [
@@ -1333,9 +1305,6 @@
1333
  "cpu": [
1334
  "riscv64"
1335
  ],
1336
- "libc": [
1337
- "glibc"
1338
- ],
1339
  "license": "Apache-2.0",
1340
  "optional": true,
1341
  "os": [
@@ -1358,9 +1327,6 @@
1358
  "cpu": [
1359
  "s390x"
1360
  ],
1361
- "libc": [
1362
- "glibc"
1363
- ],
1364
  "license": "Apache-2.0",
1365
  "optional": true,
1366
  "os": [
@@ -1383,9 +1349,6 @@
1383
  "cpu": [
1384
  "x64"
1385
  ],
1386
- "libc": [
1387
- "glibc"
1388
- ],
1389
  "license": "Apache-2.0",
1390
  "optional": true,
1391
  "os": [
@@ -1408,9 +1371,6 @@
1408
  "cpu": [
1409
  "arm64"
1410
  ],
1411
- "libc": [
1412
- "musl"
1413
- ],
1414
  "license": "Apache-2.0",
1415
  "optional": true,
1416
  "os": [
@@ -1433,9 +1393,6 @@
1433
  "cpu": [
1434
  "x64"
1435
  ],
1436
- "libc": [
1437
- "musl"
1438
- ],
1439
  "license": "Apache-2.0",
1440
  "optional": true,
1441
  "os": [
@@ -1856,9 +1813,6 @@
1856
  "cpu": [
1857
  "arm64"
1858
  ],
1859
- "libc": [
1860
- "glibc"
1861
- ],
1862
  "license": "MIT",
1863
  "optional": true,
1864
  "os": [
@@ -1879,9 +1833,6 @@
1879
  "cpu": [
1880
  "arm64"
1881
  ],
1882
- "libc": [
1883
- "musl"
1884
- ],
1885
  "license": "MIT",
1886
  "optional": true,
1887
  "os": [
@@ -1902,9 +1853,6 @@
1902
  "cpu": [
1903
  "riscv64"
1904
  ],
1905
- "libc": [
1906
- "glibc"
1907
- ],
1908
  "license": "MIT",
1909
  "optional": true,
1910
  "os": [
@@ -1925,9 +1873,6 @@
1925
  "cpu": [
1926
  "x64"
1927
  ],
1928
- "libc": [
1929
- "glibc"
1930
- ],
1931
  "license": "MIT",
1932
  "optional": true,
1933
  "os": [
@@ -1948,9 +1893,6 @@
1948
  "cpu": [
1949
  "x64"
1950
  ],
1951
- "libc": [
1952
- "musl"
1953
- ],
1954
  "license": "MIT",
1955
  "optional": true,
1956
  "os": [
@@ -2072,9 +2014,6 @@
2072
  "cpu": [
2073
  "arm64"
2074
  ],
2075
- "libc": [
2076
- "glibc"
2077
- ],
2078
  "license": "MIT",
2079
  "optional": true,
2080
  "os": [
@@ -2091,9 +2030,6 @@
2091
  "cpu": [
2092
  "arm64"
2093
  ],
2094
- "libc": [
2095
- "musl"
2096
- ],
2097
  "license": "MIT",
2098
  "optional": true,
2099
  "os": [
@@ -2110,9 +2046,6 @@
2110
  "cpu": [
2111
  "x64"
2112
  ],
2113
- "libc": [
2114
- "glibc"
2115
- ],
2116
  "license": "MIT",
2117
  "optional": true,
2118
  "os": [
@@ -2129,9 +2062,6 @@
2129
  "cpu": [
2130
  "x64"
2131
  ],
2132
- "libc": [
2133
- "musl"
2134
- ],
2135
  "license": "MIT",
2136
  "optional": true,
2137
  "os": [
@@ -2178,6 +2108,7 @@
2178
  "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
2179
  "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
2180
  "license": "MIT",
 
2181
  "engines": {
2182
  "node": "^14.21.3 || >=16"
2183
  },
@@ -2446,9 +2377,6 @@
2446
  "arm64"
2447
  ],
2448
  "dev": true,
2449
- "libc": [
2450
- "glibc"
2451
- ],
2452
  "license": "MIT",
2453
  "optional": true,
2454
  "os": [
@@ -2466,9 +2394,6 @@
2466
  "arm64"
2467
  ],
2468
  "dev": true,
2469
- "libc": [
2470
- "musl"
2471
- ],
2472
  "license": "MIT",
2473
  "optional": true,
2474
  "os": [
@@ -2486,9 +2411,6 @@
2486
  "x64"
2487
  ],
2488
  "dev": true,
2489
- "libc": [
2490
- "glibc"
2491
- ],
2492
  "license": "MIT",
2493
  "optional": true,
2494
  "os": [
@@ -2506,9 +2428,6 @@
2506
  "x64"
2507
  ],
2508
  "dev": true,
2509
- "libc": [
2510
- "musl"
2511
- ],
2512
  "license": "MIT",
2513
  "optional": true,
2514
  "os": [
@@ -2749,6 +2668,7 @@
2749
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz",
2750
  "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==",
2751
  "license": "MIT",
 
2752
  "dependencies": {
2753
  "undici-types": "~6.21.0"
2754
  }
@@ -2758,6 +2678,7 @@
2758
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
2759
  "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
2760
  "license": "MIT",
 
2761
  "dependencies": {
2762
  "csstype": "^3.2.2"
2763
  }
@@ -2844,6 +2765,7 @@
2844
  "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==",
2845
  "dev": true,
2846
  "license": "MIT",
 
2847
  "dependencies": {
2848
  "@typescript-eslint/scope-manager": "8.59.0",
2849
  "@typescript-eslint/types": "8.59.0",
@@ -3206,9 +3128,6 @@
3206
  "arm64"
3207
  ],
3208
  "dev": true,
3209
- "libc": [
3210
- "glibc"
3211
- ],
3212
  "license": "MIT",
3213
  "optional": true,
3214
  "os": [
@@ -3223,9 +3142,6 @@
3223
  "arm64"
3224
  ],
3225
  "dev": true,
3226
- "libc": [
3227
- "musl"
3228
- ],
3229
  "license": "MIT",
3230
  "optional": true,
3231
  "os": [
@@ -3240,9 +3156,6 @@
3240
  "ppc64"
3241
  ],
3242
  "dev": true,
3243
- "libc": [
3244
- "glibc"
3245
- ],
3246
  "license": "MIT",
3247
  "optional": true,
3248
  "os": [
@@ -3257,9 +3170,6 @@
3257
  "riscv64"
3258
  ],
3259
  "dev": true,
3260
- "libc": [
3261
- "glibc"
3262
- ],
3263
  "license": "MIT",
3264
  "optional": true,
3265
  "os": [
@@ -3274,9 +3184,6 @@
3274
  "riscv64"
3275
  ],
3276
  "dev": true,
3277
- "libc": [
3278
- "musl"
3279
- ],
3280
  "license": "MIT",
3281
  "optional": true,
3282
  "os": [
@@ -3291,9 +3198,6 @@
3291
  "s390x"
3292
  ],
3293
  "dev": true,
3294
- "libc": [
3295
- "glibc"
3296
- ],
3297
  "license": "MIT",
3298
  "optional": true,
3299
  "os": [
@@ -3308,9 +3212,6 @@
3308
  "x64"
3309
  ],
3310
  "dev": true,
3311
- "libc": [
3312
- "glibc"
3313
- ],
3314
  "license": "MIT",
3315
  "optional": true,
3316
  "os": [
@@ -3325,9 +3226,6 @@
3325
  "x64"
3326
  ],
3327
  "dev": true,
3328
- "libc": [
3329
- "musl"
3330
- ],
3331
  "license": "MIT",
3332
  "optional": true,
3333
  "os": [
@@ -3412,6 +3310,7 @@
3412
  "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
3413
  "dev": true,
3414
  "license": "MIT",
 
3415
  "bin": {
3416
  "acorn": "bin/acorn"
3417
  },
@@ -3866,6 +3765,7 @@
3866
  }
3867
  ],
3868
  "license": "MIT",
 
3869
  "dependencies": {
3870
  "baseline-browser-mapping": "^2.10.12",
3871
  "caniuse-lite": "^1.0.30001782",
@@ -4917,6 +4817,7 @@
4917
  "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
4918
  "dev": true,
4919
  "license": "MIT",
 
4920
  "dependencies": {
4921
  "@eslint-community/eslint-utils": "^4.8.0",
4922
  "@eslint-community/regexpp": "^4.12.1",
@@ -5102,6 +5003,7 @@
5102
  "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
5103
  "dev": true,
5104
  "license": "MIT",
 
5105
  "dependencies": {
5106
  "@rtsao/scc": "^1.1.0",
5107
  "array-includes": "^3.1.9",
@@ -5401,6 +5303,7 @@
5401
  "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
5402
  "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
5403
  "license": "MIT",
 
5404
  "dependencies": {
5405
  "accepts": "^2.0.0",
5406
  "body-parser": "^2.2.1",
@@ -6172,10 +6075,20 @@
6172
  "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
6173
  "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==",
6174
  "license": "MIT",
 
6175
  "engines": {
6176
  "node": ">=16.9.0"
6177
  }
6178
  },
 
 
 
 
 
 
 
 
 
6179
  "node_modules/html-url-attributes": {
6180
  "version": "3.0.1",
6181
  "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
@@ -6228,6 +6141,44 @@
6228
  "node": ">=18.18.0"
6229
  }
6230
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6231
  "node_modules/iconv-lite": {
6232
  "version": "0.7.2",
6233
  "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
@@ -7288,9 +7239,6 @@
7288
  "arm64"
7289
  ],
7290
  "dev": true,
7291
- "libc": [
7292
- "glibc"
7293
- ],
7294
  "license": "MPL-2.0",
7295
  "optional": true,
7296
  "os": [
@@ -7312,9 +7260,6 @@
7312
  "arm64"
7313
  ],
7314
  "dev": true,
7315
- "libc": [
7316
- "musl"
7317
- ],
7318
  "license": "MPL-2.0",
7319
  "optional": true,
7320
  "os": [
@@ -7336,9 +7281,6 @@
7336
  "x64"
7337
  ],
7338
  "dev": true,
7339
- "libc": [
7340
- "glibc"
7341
- ],
7342
  "license": "MPL-2.0",
7343
  "optional": true,
7344
  "os": [
@@ -7360,9 +7302,6 @@
7360
  "x64"
7361
  ],
7362
  "dev": true,
7363
- "libc": [
7364
- "musl"
7365
- ],
7366
  "license": "MPL-2.0",
7367
  "optional": true,
7368
  "os": [
@@ -9521,6 +9460,7 @@
9521
  "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
9522
  "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
9523
  "license": "MIT",
 
9524
  "engines": {
9525
  "node": ">=0.10.0"
9526
  }
@@ -9530,6 +9470,7 @@
9530
  "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
9531
  "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
9532
  "license": "MIT",
 
9533
  "dependencies": {
9534
  "scheduler": "^0.27.0"
9535
  },
@@ -9554,6 +9495,33 @@
9554
  "react": ">= 16.8 || 18.0.0"
9555
  }
9556
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9557
  "node_modules/react-is": {
9558
  "version": "16.13.1",
9559
  "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -10810,6 +10778,7 @@
10810
  "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
10811
  "dev": true,
10812
  "license": "MIT",
 
10813
  "engines": {
10814
  "node": ">=12"
10815
  },
@@ -11078,6 +11047,7 @@
11078
  "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
11079
  "devOptional": true,
11080
  "license": "Apache-2.0",
 
11081
  "bin": {
11082
  "tsc": "bin/tsc",
11083
  "tsserver": "bin/tsserver"
@@ -11397,6 +11367,15 @@
11397
  "url": "https://opencollective.com/unified"
11398
  }
11399
  },
 
 
 
 
 
 
 
 
 
11400
  "node_modules/warning": {
11401
  "version": "4.0.3",
11402
  "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
@@ -11737,6 +11716,7 @@
11737
  "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
11738
  "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
11739
  "license": "MIT",
 
11740
  "funding": {
11741
  "url": "https://github.com/sponsors/colinhacks"
11742
  }
 
11
  "@base-ui/react": "^1.4.1",
12
  "class-variance-authority": "^0.7.1",
13
  "clsx": "^2.1.1",
14
+ "i18next": "^26.3.0",
15
+ "i18next-browser-languagedetector": "^8.2.1",
16
  "lucide-react": "^1.8.0",
17
  "next": "16.2.4",
18
  "pdfjs-dist": "^5.6.205",
19
  "react": "19.2.4",
20
  "react-dom": "19.2.4",
21
  "react-dropzone": "^15.0.0",
22
+ "react-i18next": "^17.0.8",
23
  "react-markdown": "^10.1.0",
24
  "react-pdf": "^10.4.1",
25
  "remark-gfm": "^4.0.1",
 
79
  "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
80
  "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
81
  "license": "MIT",
82
+ "peer": true,
83
  "dependencies": {
84
  "@babel/code-frame": "^7.29.0",
85
  "@babel/generator": "^7.29.0",
 
673
  "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
674
  "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
675
  "license": "MIT",
676
+ "peer": true,
677
  "engines": {
678
  "node": ">=12"
679
  },
 
1111
  "cpu": [
1112
  "arm"
1113
  ],
 
 
 
1114
  "license": "LGPL-3.0-or-later",
1115
  "optional": true,
1116
  "os": [
 
1127
  "cpu": [
1128
  "arm64"
1129
  ],
 
 
 
1130
  "license": "LGPL-3.0-or-later",
1131
  "optional": true,
1132
  "os": [
 
1143
  "cpu": [
1144
  "ppc64"
1145
  ],
 
 
 
1146
  "license": "LGPL-3.0-or-later",
1147
  "optional": true,
1148
  "os": [
 
1159
  "cpu": [
1160
  "riscv64"
1161
  ],
 
 
 
1162
  "license": "LGPL-3.0-or-later",
1163
  "optional": true,
1164
  "os": [
 
1175
  "cpu": [
1176
  "s390x"
1177
  ],
 
 
 
1178
  "license": "LGPL-3.0-or-later",
1179
  "optional": true,
1180
  "os": [
 
1191
  "cpu": [
1192
  "x64"
1193
  ],
 
 
 
1194
  "license": "LGPL-3.0-or-later",
1195
  "optional": true,
1196
  "os": [
 
1207
  "cpu": [
1208
  "arm64"
1209
  ],
 
 
 
1210
  "license": "LGPL-3.0-or-later",
1211
  "optional": true,
1212
  "os": [
 
1223
  "cpu": [
1224
  "x64"
1225
  ],
 
 
 
1226
  "license": "LGPL-3.0-or-later",
1227
  "optional": true,
1228
  "os": [
 
1239
  "cpu": [
1240
  "arm"
1241
  ],
 
 
 
1242
  "license": "Apache-2.0",
1243
  "optional": true,
1244
  "os": [
 
1261
  "cpu": [
1262
  "arm64"
1263
  ],
 
 
 
1264
  "license": "Apache-2.0",
1265
  "optional": true,
1266
  "os": [
 
1283
  "cpu": [
1284
  "ppc64"
1285
  ],
 
 
 
1286
  "license": "Apache-2.0",
1287
  "optional": true,
1288
  "os": [
 
1305
  "cpu": [
1306
  "riscv64"
1307
  ],
 
 
 
1308
  "license": "Apache-2.0",
1309
  "optional": true,
1310
  "os": [
 
1327
  "cpu": [
1328
  "s390x"
1329
  ],
 
 
 
1330
  "license": "Apache-2.0",
1331
  "optional": true,
1332
  "os": [
 
1349
  "cpu": [
1350
  "x64"
1351
  ],
 
 
 
1352
  "license": "Apache-2.0",
1353
  "optional": true,
1354
  "os": [
 
1371
  "cpu": [
1372
  "arm64"
1373
  ],
 
 
 
1374
  "license": "Apache-2.0",
1375
  "optional": true,
1376
  "os": [
 
1393
  "cpu": [
1394
  "x64"
1395
  ],
 
 
 
1396
  "license": "Apache-2.0",
1397
  "optional": true,
1398
  "os": [
 
1813
  "cpu": [
1814
  "arm64"
1815
  ],
 
 
 
1816
  "license": "MIT",
1817
  "optional": true,
1818
  "os": [
 
1833
  "cpu": [
1834
  "arm64"
1835
  ],
 
 
 
1836
  "license": "MIT",
1837
  "optional": true,
1838
  "os": [
 
1853
  "cpu": [
1854
  "riscv64"
1855
  ],
 
 
 
1856
  "license": "MIT",
1857
  "optional": true,
1858
  "os": [
 
1873
  "cpu": [
1874
  "x64"
1875
  ],
 
 
 
1876
  "license": "MIT",
1877
  "optional": true,
1878
  "os": [
 
1893
  "cpu": [
1894
  "x64"
1895
  ],
 
 
 
1896
  "license": "MIT",
1897
  "optional": true,
1898
  "os": [
 
2014
  "cpu": [
2015
  "arm64"
2016
  ],
 
 
 
2017
  "license": "MIT",
2018
  "optional": true,
2019
  "os": [
 
2030
  "cpu": [
2031
  "arm64"
2032
  ],
 
 
 
2033
  "license": "MIT",
2034
  "optional": true,
2035
  "os": [
 
2046
  "cpu": [
2047
  "x64"
2048
  ],
 
 
 
2049
  "license": "MIT",
2050
  "optional": true,
2051
  "os": [
 
2062
  "cpu": [
2063
  "x64"
2064
  ],
 
 
 
2065
  "license": "MIT",
2066
  "optional": true,
2067
  "os": [
 
2108
  "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
2109
  "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
2110
  "license": "MIT",
2111
+ "peer": true,
2112
  "engines": {
2113
  "node": "^14.21.3 || >=16"
2114
  },
 
2377
  "arm64"
2378
  ],
2379
  "dev": true,
 
 
 
2380
  "license": "MIT",
2381
  "optional": true,
2382
  "os": [
 
2394
  "arm64"
2395
  ],
2396
  "dev": true,
 
 
 
2397
  "license": "MIT",
2398
  "optional": true,
2399
  "os": [
 
2411
  "x64"
2412
  ],
2413
  "dev": true,
 
 
 
2414
  "license": "MIT",
2415
  "optional": true,
2416
  "os": [
 
2428
  "x64"
2429
  ],
2430
  "dev": true,
 
 
 
2431
  "license": "MIT",
2432
  "optional": true,
2433
  "os": [
 
2668
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz",
2669
  "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==",
2670
  "license": "MIT",
2671
+ "peer": true,
2672
  "dependencies": {
2673
  "undici-types": "~6.21.0"
2674
  }
 
2678
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
2679
  "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
2680
  "license": "MIT",
2681
+ "peer": true,
2682
  "dependencies": {
2683
  "csstype": "^3.2.2"
2684
  }
 
2765
  "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==",
2766
  "dev": true,
2767
  "license": "MIT",
2768
+ "peer": true,
2769
  "dependencies": {
2770
  "@typescript-eslint/scope-manager": "8.59.0",
2771
  "@typescript-eslint/types": "8.59.0",
 
3128
  "arm64"
3129
  ],
3130
  "dev": true,
 
 
 
3131
  "license": "MIT",
3132
  "optional": true,
3133
  "os": [
 
3142
  "arm64"
3143
  ],
3144
  "dev": true,
 
 
 
3145
  "license": "MIT",
3146
  "optional": true,
3147
  "os": [
 
3156
  "ppc64"
3157
  ],
3158
  "dev": true,
 
 
 
3159
  "license": "MIT",
3160
  "optional": true,
3161
  "os": [
 
3170
  "riscv64"
3171
  ],
3172
  "dev": true,
 
 
 
3173
  "license": "MIT",
3174
  "optional": true,
3175
  "os": [
 
3184
  "riscv64"
3185
  ],
3186
  "dev": true,
 
 
 
3187
  "license": "MIT",
3188
  "optional": true,
3189
  "os": [
 
3198
  "s390x"
3199
  ],
3200
  "dev": true,
 
 
 
3201
  "license": "MIT",
3202
  "optional": true,
3203
  "os": [
 
3212
  "x64"
3213
  ],
3214
  "dev": true,
 
 
 
3215
  "license": "MIT",
3216
  "optional": true,
3217
  "os": [
 
3226
  "x64"
3227
  ],
3228
  "dev": true,
 
 
 
3229
  "license": "MIT",
3230
  "optional": true,
3231
  "os": [
 
3310
  "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
3311
  "dev": true,
3312
  "license": "MIT",
3313
+ "peer": true,
3314
  "bin": {
3315
  "acorn": "bin/acorn"
3316
  },
 
3765
  }
3766
  ],
3767
  "license": "MIT",
3768
+ "peer": true,
3769
  "dependencies": {
3770
  "baseline-browser-mapping": "^2.10.12",
3771
  "caniuse-lite": "^1.0.30001782",
 
4817
  "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
4818
  "dev": true,
4819
  "license": "MIT",
4820
+ "peer": true,
4821
  "dependencies": {
4822
  "@eslint-community/eslint-utils": "^4.8.0",
4823
  "@eslint-community/regexpp": "^4.12.1",
 
5003
  "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
5004
  "dev": true,
5005
  "license": "MIT",
5006
+ "peer": true,
5007
  "dependencies": {
5008
  "@rtsao/scc": "^1.1.0",
5009
  "array-includes": "^3.1.9",
 
5303
  "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
5304
  "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
5305
  "license": "MIT",
5306
+ "peer": true,
5307
  "dependencies": {
5308
  "accepts": "^2.0.0",
5309
  "body-parser": "^2.2.1",
 
6075
  "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
6076
  "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==",
6077
  "license": "MIT",
6078
+ "peer": true,
6079
  "engines": {
6080
  "node": ">=16.9.0"
6081
  }
6082
  },
6083
+ "node_modules/html-parse-stringify": {
6084
+ "version": "3.0.1",
6085
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
6086
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
6087
+ "license": "MIT",
6088
+ "dependencies": {
6089
+ "void-elements": "3.1.0"
6090
+ }
6091
+ },
6092
  "node_modules/html-url-attributes": {
6093
  "version": "3.0.1",
6094
  "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
 
6141
  "node": ">=18.18.0"
6142
  }
6143
  },
6144
+ "node_modules/i18next": {
6145
+ "version": "26.3.0",
6146
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.0.tgz",
6147
+ "integrity": "sha512-gHSgGpUXVmuqE2El1W61DmxeyeTlFfZgdJRWMo9jScAn5pu7TuTuiccb1zh3E2J9hEBVGJ23+96x0ieBhfuIHA==",
6148
+ "funding": [
6149
+ {
6150
+ "type": "individual",
6151
+ "url": "https://www.locize.com/i18next"
6152
+ },
6153
+ {
6154
+ "type": "individual",
6155
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
6156
+ },
6157
+ {
6158
+ "type": "individual",
6159
+ "url": "https://www.locize.com"
6160
+ }
6161
+ ],
6162
+ "license": "MIT",
6163
+ "peer": true,
6164
+ "peerDependencies": {
6165
+ "typescript": "^5 || ^6"
6166
+ },
6167
+ "peerDependenciesMeta": {
6168
+ "typescript": {
6169
+ "optional": true
6170
+ }
6171
+ }
6172
+ },
6173
+ "node_modules/i18next-browser-languagedetector": {
6174
+ "version": "8.2.1",
6175
+ "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz",
6176
+ "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==",
6177
+ "license": "MIT",
6178
+ "dependencies": {
6179
+ "@babel/runtime": "^7.23.2"
6180
+ }
6181
+ },
6182
  "node_modules/iconv-lite": {
6183
  "version": "0.7.2",
6184
  "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
 
7239
  "arm64"
7240
  ],
7241
  "dev": true,
 
 
 
7242
  "license": "MPL-2.0",
7243
  "optional": true,
7244
  "os": [
 
7260
  "arm64"
7261
  ],
7262
  "dev": true,
 
 
 
7263
  "license": "MPL-2.0",
7264
  "optional": true,
7265
  "os": [
 
7281
  "x64"
7282
  ],
7283
  "dev": true,
 
 
 
7284
  "license": "MPL-2.0",
7285
  "optional": true,
7286
  "os": [
 
7302
  "x64"
7303
  ],
7304
  "dev": true,
 
 
 
7305
  "license": "MPL-2.0",
7306
  "optional": true,
7307
  "os": [
 
9460
  "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
9461
  "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
9462
  "license": "MIT",
9463
+ "peer": true,
9464
  "engines": {
9465
  "node": ">=0.10.0"
9466
  }
 
9470
  "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
9471
  "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
9472
  "license": "MIT",
9473
+ "peer": true,
9474
  "dependencies": {
9475
  "scheduler": "^0.27.0"
9476
  },
 
9495
  "react": ">= 16.8 || 18.0.0"
9496
  }
9497
  },
9498
+ "node_modules/react-i18next": {
9499
+ "version": "17.0.8",
9500
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.8.tgz",
9501
+ "integrity": "sha512-0ooKbGLU8JXhe1zwpQUWIeXSgLPOfwJmgheWRIUpcoA0CpyabpGhayjdG+/eA5esC1AQ8h2jWpXjJfzQzeDOCw==",
9502
+ "license": "MIT",
9503
+ "dependencies": {
9504
+ "@babel/runtime": "^7.29.2",
9505
+ "html-parse-stringify": "^3.0.1",
9506
+ "use-sync-external-store": "^1.6.0"
9507
+ },
9508
+ "peerDependencies": {
9509
+ "i18next": ">= 26.2.0",
9510
+ "react": ">= 16.8.0",
9511
+ "typescript": "^5 || ^6"
9512
+ },
9513
+ "peerDependenciesMeta": {
9514
+ "react-dom": {
9515
+ "optional": true
9516
+ },
9517
+ "react-native": {
9518
+ "optional": true
9519
+ },
9520
+ "typescript": {
9521
+ "optional": true
9522
+ }
9523
+ }
9524
+ },
9525
  "node_modules/react-is": {
9526
  "version": "16.13.1",
9527
  "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
 
10778
  "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
10779
  "dev": true,
10780
  "license": "MIT",
10781
+ "peer": true,
10782
  "engines": {
10783
  "node": ">=12"
10784
  },
 
11047
  "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
11048
  "devOptional": true,
11049
  "license": "Apache-2.0",
11050
+ "peer": true,
11051
  "bin": {
11052
  "tsc": "bin/tsc",
11053
  "tsserver": "bin/tsserver"
 
11367
  "url": "https://opencollective.com/unified"
11368
  }
11369
  },
11370
+ "node_modules/void-elements": {
11371
+ "version": "3.1.0",
11372
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
11373
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
11374
+ "license": "MIT",
11375
+ "engines": {
11376
+ "node": ">=0.10.0"
11377
+ }
11378
+ },
11379
  "node_modules/warning": {
11380
  "version": "4.0.3",
11381
  "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
 
11716
  "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
11717
  "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
11718
  "license": "MIT",
11719
+ "peer": true,
11720
  "funding": {
11721
  "url": "https://github.com/sponsors/colinhacks"
11722
  }
frontend/package.json CHANGED
@@ -12,12 +12,15 @@
12
  "@base-ui/react": "^1.4.1",
13
  "class-variance-authority": "^0.7.1",
14
  "clsx": "^2.1.1",
 
 
15
  "lucide-react": "^1.8.0",
16
  "next": "16.2.4",
17
  "pdfjs-dist": "^5.6.205",
18
  "react": "19.2.4",
19
  "react-dom": "19.2.4",
20
  "react-dropzone": "^15.0.0",
 
21
  "react-markdown": "^10.1.0",
22
  "react-pdf": "^10.4.1",
23
  "remark-gfm": "^4.0.1",
 
12
  "@base-ui/react": "^1.4.1",
13
  "class-variance-authority": "^0.7.1",
14
  "clsx": "^2.1.1",
15
+ "i18next": "^26.3.0",
16
+ "i18next-browser-languagedetector": "^8.2.1",
17
  "lucide-react": "^1.8.0",
18
  "next": "16.2.4",
19
  "pdfjs-dist": "^5.6.205",
20
  "react": "19.2.4",
21
  "react-dom": "19.2.4",
22
  "react-dropzone": "^15.0.0",
23
+ "react-i18next": "^17.0.8",
24
  "react-markdown": "^10.1.0",
25
  "react-pdf": "^10.4.1",
26
  "remark-gfm": "^4.0.1",
frontend/src/app/layout.tsx CHANGED
@@ -3,6 +3,7 @@ import { Inter } from "next/font/google";
3
  import "./globals.css";
4
  import { AuthProvider } from "@/lib/auth";
5
  import { TooltipProvider } from "@/components/ui/tooltip";
 
6
 
7
  const inter = Inter({
8
  variable: "--font-sans",
@@ -26,9 +27,11 @@ export default function RootLayout({
26
  <html lang="en" className={`${inter.variable} dark h-full antialiased`}>
27
  <body className="min-h-full flex flex-col bg-background text-foreground">
28
  <AuthProvider>
29
- <TooltipProvider>
30
- {children}
31
- </TooltipProvider>
 
 
32
  </AuthProvider>
33
  </body>
34
  </html>
 
3
  import "./globals.css";
4
  import { AuthProvider } from "@/lib/auth";
5
  import { TooltipProvider } from "@/components/ui/tooltip";
6
+ import I18nProvider from "@/components/providers/I18nProvider";
7
 
8
  const inter = Inter({
9
  variable: "--font-sans",
 
27
  <html lang="en" className={`${inter.variable} dark h-full antialiased`}>
28
  <body className="min-h-full flex flex-col bg-background text-foreground">
29
  <AuthProvider>
30
+ <I18nProvider>
31
+ <TooltipProvider>
32
+ {children}
33
+ </TooltipProvider>
34
+ </I18nProvider>
35
  </AuthProvider>
36
  </body>
37
  </html>
frontend/src/app/login/page.tsx CHANGED
@@ -3,6 +3,7 @@
3
  import { useState } from "react";
4
  import { useRouter } from "next/navigation";
5
  import { useAuth } from "@/lib/auth";
 
6
  import { Button } from "@/components/ui/button";
7
  import { Input } from "@/components/ui/input";
8
  import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
@@ -11,6 +12,7 @@ import Link from "next/link";
11
 
12
  export default function LoginPage() {
13
  const { login } = useAuth();
 
14
  const router = useRouter();
15
  const [email, setEmail] = useState("");
16
  const [password, setPassword] = useState("");
@@ -27,7 +29,7 @@ export default function LoginPage() {
27
  await login(email, password);
28
  router.replace("/dashboard");
29
  } catch (err: unknown) {
30
- const message = err instanceof Error ? err.message : "Login failed";
31
  setError(message);
32
  } finally {
33
  setLoading(false);
@@ -46,8 +48,8 @@ export default function LoginPage() {
46
  <Brain className="w-6 h-6 text-primary" />
47
  </div>
48
  </div>
49
- <CardTitle className="text-2xl font-bold">Welcome back</CardTitle>
50
- <CardDescription>Sign in to your Document AI Analyst account</CardDescription>
51
  </CardHeader>
52
 
53
  <CardContent>
@@ -59,7 +61,7 @@ export default function LoginPage() {
59
  )}
60
 
61
  <div className="space-y-2">
62
- <label className="text-sm font-medium">Email</label>
63
  <Input
64
  id="login-email"
65
  type="email"
@@ -72,7 +74,7 @@ export default function LoginPage() {
72
  </div>
73
 
74
  <div className="space-y-2">
75
- <label className="text-sm font-medium">Password</label>
76
  <div className="relative">
77
  <Input
78
  id="login-password"
@@ -97,18 +99,18 @@ export default function LoginPage() {
97
  {loading ? (
98
  <span className="flex items-center gap-2">
99
  <span className="w-4 h-4 border-2 border-primary-foreground/30 border-t-primary-foreground rounded-full animate-spin" />
100
- Signing in...
101
  </span>
102
  ) : (
103
- "Sign In"
104
  )}
105
  </Button>
106
  </form>
107
 
108
  <p className="text-center text-sm text-muted-foreground mt-6">
109
- Don&apos;t have an account?{" "}
110
  <Link href="/register" className="text-primary hover:underline font-medium">
111
- Create one
112
  </Link>
113
  </p>
114
  </CardContent>
 
3
  import { useState } from "react";
4
  import { useRouter } from "next/navigation";
5
  import { useAuth } from "@/lib/auth";
6
+ import { useTranslation } from "react-i18next";
7
  import { Button } from "@/components/ui/button";
8
  import { Input } from "@/components/ui/input";
9
  import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
 
12
 
13
  export default function LoginPage() {
14
  const { login } = useAuth();
15
+ const { t } = useTranslation();
16
  const router = useRouter();
17
  const [email, setEmail] = useState("");
18
  const [password, setPassword] = useState("");
 
29
  await login(email, password);
30
  router.replace("/dashboard");
31
  } catch (err: unknown) {
32
+ const message = err instanceof Error ? err.message : t("login.fallbackError");
33
  setError(message);
34
  } finally {
35
  setLoading(false);
 
48
  <Brain className="w-6 h-6 text-primary" />
49
  </div>
50
  </div>
51
+ <CardTitle className="text-2xl font-bold">{t("login.title")}</CardTitle>
52
+ <CardDescription>{t("login.description")}</CardDescription>
53
  </CardHeader>
54
 
55
  <CardContent>
 
61
  )}
62
 
63
  <div className="space-y-2">
64
+ <label className="text-sm font-medium">{t("common.email")}</label>
65
  <Input
66
  id="login-email"
67
  type="email"
 
74
  </div>
75
 
76
  <div className="space-y-2">
77
+ <label className="text-sm font-medium">{t("common.password")}</label>
78
  <div className="relative">
79
  <Input
80
  id="login-password"
 
99
  {loading ? (
100
  <span className="flex items-center gap-2">
101
  <span className="w-4 h-4 border-2 border-primary-foreground/30 border-t-primary-foreground rounded-full animate-spin" />
102
+ {t("login.submitting")}
103
  </span>
104
  ) : (
105
+ t("login.submit")
106
  )}
107
  </Button>
108
  </form>
109
 
110
  <p className="text-center text-sm text-muted-foreground mt-6">
111
+ {t("login.noAccount")}{" "}
112
  <Link href="/register" className="text-primary hover:underline font-medium">
113
+ {t("login.createOne")}
114
  </Link>
115
  </p>
116
  </CardContent>
frontend/src/app/register/page.tsx CHANGED
@@ -3,6 +3,7 @@
3
  import { useState } from "react";
4
  import { useRouter } from "next/navigation";
5
  import { useAuth } from "@/lib/auth";
 
6
  import { Button } from "@/components/ui/button";
7
  import { Input } from "@/components/ui/input";
8
  import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
@@ -11,6 +12,7 @@ import Link from "next/link";
11
 
12
  export default function RegisterPage() {
13
  const { register } = useAuth();
 
14
  const router = useRouter();
15
  const [username, setUsername] = useState("");
16
  const [email, setEmail] = useState("");
@@ -28,7 +30,7 @@ export default function RegisterPage() {
28
  await register(username, email, password);
29
  router.replace("/dashboard");
30
  } catch (err: unknown) {
31
- const message = err instanceof Error ? err.message : "Registration failed";
32
  setError(message);
33
  } finally {
34
  setLoading(false);
@@ -46,8 +48,8 @@ export default function RegisterPage() {
46
  <Brain className="w-6 h-6 text-primary" />
47
  </div>
48
  </div>
49
- <CardTitle className="text-2xl font-bold">Create Account</CardTitle>
50
- <CardDescription>Start analyzing documents with AI</CardDescription>
51
  </CardHeader>
52
 
53
  <CardContent>
@@ -59,7 +61,7 @@ export default function RegisterPage() {
59
  )}
60
 
61
  <div className="space-y-2">
62
- <label className="text-sm font-medium">Username</label>
63
  <Input
64
  id="reg-username"
65
  type="text"
@@ -73,7 +75,7 @@ export default function RegisterPage() {
73
  </div>
74
 
75
  <div className="space-y-2">
76
- <label className="text-sm font-medium">Email</label>
77
  <Input
78
  id="reg-email"
79
  type="email"
@@ -86,12 +88,12 @@ export default function RegisterPage() {
86
  </div>
87
 
88
  <div className="space-y-2">
89
- <label className="text-sm font-medium">Password</label>
90
  <div className="relative">
91
  <Input
92
  id="reg-password"
93
  type={showPw ? "text" : "password"}
94
- placeholder="Minimum 6 characters"
95
  value={password}
96
  onChange={(e) => setPassword(e.target.value)}
97
  required
@@ -112,18 +114,18 @@ export default function RegisterPage() {
112
  {loading ? (
113
  <span className="flex items-center gap-2">
114
  <span className="w-4 h-4 border-2 border-primary-foreground/30 border-t-primary-foreground rounded-full animate-spin" />
115
- Creating account...
116
  </span>
117
  ) : (
118
- "Create Account"
119
  )}
120
  </Button>
121
  </form>
122
 
123
  <p className="text-center text-sm text-muted-foreground mt-6">
124
- Already have an account?{" "}
125
  <Link href="/login" className="text-primary hover:underline font-medium">
126
- Sign in
127
  </Link>
128
  </p>
129
  </CardContent>
 
3
  import { useState } from "react";
4
  import { useRouter } from "next/navigation";
5
  import { useAuth } from "@/lib/auth";
6
+ import { useTranslation } from "react-i18next";
7
  import { Button } from "@/components/ui/button";
8
  import { Input } from "@/components/ui/input";
9
  import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
 
12
 
13
  export default function RegisterPage() {
14
  const { register } = useAuth();
15
+ const { t } = useTranslation();
16
  const router = useRouter();
17
  const [username, setUsername] = useState("");
18
  const [email, setEmail] = useState("");
 
30
  await register(username, email, password);
31
  router.replace("/dashboard");
32
  } catch (err: unknown) {
33
+ const message = err instanceof Error ? err.message : t("register.fallbackError");
34
  setError(message);
35
  } finally {
36
  setLoading(false);
 
48
  <Brain className="w-6 h-6 text-primary" />
49
  </div>
50
  </div>
51
+ <CardTitle className="text-2xl font-bold">{t("register.title")}</CardTitle>
52
+ <CardDescription>{t("register.description")}</CardDescription>
53
  </CardHeader>
54
 
55
  <CardContent>
 
61
  )}
62
 
63
  <div className="space-y-2">
64
+ <label className="text-sm font-medium">{t("common.username")}</label>
65
  <Input
66
  id="reg-username"
67
  type="text"
 
75
  </div>
76
 
77
  <div className="space-y-2">
78
+ <label className="text-sm font-medium">{t("common.email")}</label>
79
  <Input
80
  id="reg-email"
81
  type="email"
 
88
  </div>
89
 
90
  <div className="space-y-2">
91
+ <label className="text-sm font-medium">{t("common.password")}</label>
92
  <div className="relative">
93
  <Input
94
  id="reg-password"
95
  type={showPw ? "text" : "password"}
96
+ placeholder={t("register.passwordPlaceholder")}
97
  value={password}
98
  onChange={(e) => setPassword(e.target.value)}
99
  required
 
114
  {loading ? (
115
  <span className="flex items-center gap-2">
116
  <span className="w-4 h-4 border-2 border-primary-foreground/30 border-t-primary-foreground rounded-full animate-spin" />
117
+ {t("register.submitting")}
118
  </span>
119
  ) : (
120
+ t("register.submit")
121
  )}
122
  </Button>
123
  </form>
124
 
125
  <p className="text-center text-sm text-muted-foreground mt-6">
126
+ {t("register.hasAccount")}{" "}
127
  <Link href="/login" className="text-primary hover:underline font-medium">
128
+ {t("register.signIn")}
129
  </Link>
130
  </p>
131
  </CardContent>
frontend/src/components/chat/ChatPanel.tsx CHANGED
@@ -1,6 +1,7 @@
1
  "use client";
2
 
3
  import { useState, useRef, useEffect } from "react";
 
4
  import type { DocInfo } from "@/app/dashboard/page";
5
  import { api, API_BASE } from "@/lib/api";
6
  import { Button } from "@/components/ui/button";
@@ -31,6 +32,7 @@ interface Props {
31
  }
32
 
33
  export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
 
34
  const [messages, setMessages] = useState<ChatMsg[]>([]);
35
  const [input, setInput] = useState("");
36
  const [streaming, setStreaming] = useState(false);
@@ -189,7 +191,9 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
189
  m.id === assistantId
190
  ? {
191
  ...m,
192
- content: `Failed to get response: ${err instanceof Error ? err.message : "Unknown error"}`,
 
 
193
  isStreaming: false,
194
  }
195
  : m
@@ -202,7 +206,7 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
202
  };
203
 
204
  const handleClear = async () => {
205
- if (!activeDoc || !confirm("Clear all chat history for this document?")) return;
206
  try {
207
  await api.delete(`/api/v1/chat/history/${activeDoc.id}`);
208
  setMessages([]);
@@ -254,12 +258,12 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
254
  <MessageSquare className="w-8 h-8 text-primary/60" />
255
  </div>
256
  <h3 className="text-lg font-semibold mb-1">
257
- {activeDoc ? "Ask about your document" : "Select a document"}
258
  </h3>
259
  <p className="text-sm text-muted-foreground text-center max-w-sm">
260
  {activeDoc
261
- ? `"${activeDoc.original_name}" is ready. Ask any question and get cited answers.`
262
- : "Upload and select a document from the sidebar to start chatting."}
263
  </p>
264
  </div>
265
  ) : (
@@ -297,8 +301,8 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
297
  onKeyDown={handleKeyDown}
298
  placeholder={
299
  activeDoc
300
- ? `Ask about "${activeDoc.original_name}"...`
301
- : "Select a document first..."
302
  }
303
  disabled={streaming}
304
  className="min-h-[44px] max-h-32 resize-none bg-background/50 border-border/50"
@@ -328,7 +332,7 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
328
  size="icon"
329
  onClick={() => setShowExportMenu((v) => !v)}
330
  className="h-[44px] w-[44px] text-muted-foreground hover:text-primary"
331
- title="Export chat history"
332
  >
333
  <Download className="w-4 h-4" />
334
  </Button>
@@ -340,7 +344,7 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
340
  className="w-full flex items-center gap-2 rounded-md px-3 py-2 text-sm hover:bg-accent transition-colors text-left"
341
  >
342
  <span className="text-base">📝</span>
343
- Markdown (.md)
344
  </button>
345
  <button
346
  id="export-txt-btn"
@@ -348,7 +352,7 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
348
  className="w-full flex items-center gap-2 rounded-md px-3 py-2 text-sm hover:bg-accent transition-colors text-left"
349
  >
350
  <span className="text-base">📄</span>
351
- Plain Text (.txt)
352
  </button>
353
  </div>
354
  )}
@@ -369,4 +373,4 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
369
  </div>
370
  </div>
371
  );
372
- }
 
1
  "use client";
2
 
3
  import { useState, useRef, useEffect } from "react";
4
+ import { useTranslation } from "react-i18next";
5
  import type { DocInfo } from "@/app/dashboard/page";
6
  import { api, API_BASE } from "@/lib/api";
7
  import { Button } from "@/components/ui/button";
 
32
  }
33
 
34
  export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
35
+ const { t } = useTranslation();
36
  const [messages, setMessages] = useState<ChatMsg[]>([]);
37
  const [input, setInput] = useState("");
38
  const [streaming, setStreaming] = useState(false);
 
191
  m.id === assistantId
192
  ? {
193
  ...m,
194
+ content: t("chat.fallbackError", {
195
+ message: err instanceof Error ? err.message : "Unknown error",
196
+ }),
197
  isStreaming: false,
198
  }
199
  : m
 
206
  };
207
 
208
  const handleClear = async () => {
209
+ if (!activeDoc || !confirm(t("chat.clearConfirm"))) return;
210
  try {
211
  await api.delete(`/api/v1/chat/history/${activeDoc.id}`);
212
  setMessages([]);
 
258
  <MessageSquare className="w-8 h-8 text-primary/60" />
259
  </div>
260
  <h3 className="text-lg font-semibold mb-1">
261
+ {activeDoc ? t("chat.askAboutDocument") : t("chat.selectDocument")}
262
  </h3>
263
  <p className="text-sm text-muted-foreground text-center max-w-sm">
264
  {activeDoc
265
+ ? t("chat.readyPrompt", { name: activeDoc.original_name })
266
+ : t("chat.uploadPrompt")}
267
  </p>
268
  </div>
269
  ) : (
 
301
  onKeyDown={handleKeyDown}
302
  placeholder={
303
  activeDoc
304
+ ? t("chat.askPlaceholder", { name: activeDoc.original_name })
305
+ : t("chat.selectPlaceholder")
306
  }
307
  disabled={streaming}
308
  className="min-h-[44px] max-h-32 resize-none bg-background/50 border-border/50"
 
332
  size="icon"
333
  onClick={() => setShowExportMenu((v) => !v)}
334
  className="h-[44px] w-[44px] text-muted-foreground hover:text-primary"
335
+ title={t("chat.exportTitle")}
336
  >
337
  <Download className="w-4 h-4" />
338
  </Button>
 
344
  className="w-full flex items-center gap-2 rounded-md px-3 py-2 text-sm hover:bg-accent transition-colors text-left"
345
  >
346
  <span className="text-base">📝</span>
347
+ {t("chat.markdown")}
348
  </button>
349
  <button
350
  id="export-txt-btn"
 
352
  className="w-full flex items-center gap-2 rounded-md px-3 py-2 text-sm hover:bg-accent transition-colors text-left"
353
  >
354
  <span className="text-base">📄</span>
355
+ {t("chat.plainText")}
356
  </button>
357
  </div>
358
  )}
 
373
  </div>
374
  </div>
375
  );
376
+ }
frontend/src/components/document/DocumentSidebar.tsx CHANGED
@@ -1,6 +1,7 @@
1
  "use client";
2
 
3
  import { useState, useCallback } from "react";
 
4
  import type { DocInfo } from "@/app/dashboard/page";
5
  import { api } from "@/lib/api";
6
  import { ScrollArea } from "@/components/ui/scroll-area";
@@ -20,6 +21,7 @@ interface Props {
20
  }
21
 
22
  export default function DocumentSidebar({ documents = [], activeDoc, onSelectDoc, onDocumentsChange }: Props) {
 
23
  const [uploading, setUploading] = useState(false);
24
  const [uploadProgress, setUploadProgress] = useState(0);
25
  const [uploadError, setUploadError] = useState("");
@@ -43,7 +45,7 @@ export default function DocumentSidebar({ documents = [], activeDoc, onSelectDoc
43
  }
44
  onDocumentsChange();
45
  } catch (err) {
46
- const message = err instanceof Error ? err.message : "Upload failed";
47
  setUploadError(message);
48
  } finally {
49
  setUploading(false);
@@ -51,7 +53,7 @@ export default function DocumentSidebar({ documents = [], activeDoc, onSelectDoc
51
  }
52
  })();
53
  },
54
- [onDocumentsChange]
55
  );
56
 
57
  const { getRootProps, getInputProps, isDragActive } = useDropzone({
@@ -67,7 +69,7 @@ export default function DocumentSidebar({ documents = [], activeDoc, onSelectDoc
67
 
68
  const handleDelete = async (docId: string, e: React.MouseEvent) => {
69
  e.stopPropagation();
70
- if (!confirm("Delete this document and all its data?")) return;
71
  setDeleting(docId);
72
  try {
73
  await api.delete(`/api/v1/documents/${docId}`);
@@ -119,17 +121,17 @@ export default function DocumentSidebar({ documents = [], activeDoc, onSelectDoc
119
  {uploading ? (
120
  <div className="space-y-2">
121
  <Loader2 className="w-5 h-5 mx-auto animate-spin text-primary" />
122
- <p className="text-xs text-muted-foreground">Uploading...</p>
123
  <Progress value={uploadProgress} className="h-1" />
124
  </div>
125
  ) : (
126
  <>
127
  <Upload className="w-5 h-5 mx-auto text-muted-foreground mb-2" />
128
  <p className="text-xs text-muted-foreground">
129
- {isDragActive ? "Drop files here" : "Drop files or click to upload"}
130
  </p>
131
  <p className="text-[10px] text-muted-foreground/60 mt-1">
132
- PDF, DOCX, TXT, MD (max 50MB)
133
  </p>
134
  </>
135
  )}
@@ -139,7 +141,7 @@ export default function DocumentSidebar({ documents = [], activeDoc, onSelectDoc
139
  {/* ── Documents List ──────────────────────────── */}
140
  <div className="px-3 pt-3 pb-1">
141
  <h3 className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground mb-2">
142
- Documents ({documents.length})
143
  </h3>
144
  </div>
145
 
@@ -147,8 +149,8 @@ export default function DocumentSidebar({ documents = [], activeDoc, onSelectDoc
147
  {documents.length === 0 ? (
148
  <div className="text-center py-12">
149
  <FolderOpen className="w-8 h-8 mx-auto text-muted-foreground/40 mb-3" />
150
- <p className="text-sm text-muted-foreground">No documents yet</p>
151
- <p className="text-xs text-muted-foreground/60 mt-1">Upload a file to get started</p>
152
  </div>
153
  ) : (
154
  <div className="space-y-1 pb-3">
@@ -176,22 +178,22 @@ export default function DocumentSidebar({ documents = [], activeDoc, onSelectDoc
176
  <>
177
  <span className="text-[10px] text-muted-foreground">•</span>
178
  <span className="text-[10px] text-muted-foreground">
179
- {doc.page_count} pg
180
  </span>
181
  <span className="text-[10px] text-muted-foreground">•</span>
182
  <span className="text-[10px] text-muted-foreground">
183
- {doc.chunk_count} chunks
184
  </span>
185
  </>
186
  )}
187
  {doc.status === "processing" && (
188
  <Badge variant="secondary" className="text-[9px] h-4 px-1.5">
189
- Processing
190
  </Badge>
191
  )}
192
  {doc.status === "failed" && (
193
  <Badge variant="destructive" className="text-[9px] h-4 px-1.5">
194
- Failed
195
  </Badge>
196
  )}
197
  </div>
 
1
  "use client";
2
 
3
  import { useState, useCallback } from "react";
4
+ import { useTranslation } from "react-i18next";
5
  import type { DocInfo } from "@/app/dashboard/page";
6
  import { api } from "@/lib/api";
7
  import { ScrollArea } from "@/components/ui/scroll-area";
 
21
  }
22
 
23
  export default function DocumentSidebar({ documents = [], activeDoc, onSelectDoc, onDocumentsChange }: Props) {
24
+ const { t } = useTranslation();
25
  const [uploading, setUploading] = useState(false);
26
  const [uploadProgress, setUploadProgress] = useState(0);
27
  const [uploadError, setUploadError] = useState("");
 
45
  }
46
  onDocumentsChange();
47
  } catch (err) {
48
+ const message = err instanceof Error ? err.message : t("documents.uploadFailed");
49
  setUploadError(message);
50
  } finally {
51
  setUploading(false);
 
53
  }
54
  })();
55
  },
56
+ [onDocumentsChange, t]
57
  );
58
 
59
  const { getRootProps, getInputProps, isDragActive } = useDropzone({
 
69
 
70
  const handleDelete = async (docId: string, e: React.MouseEvent) => {
71
  e.stopPropagation();
72
+ if (!confirm(t("documents.deleteConfirm"))) return;
73
  setDeleting(docId);
74
  try {
75
  await api.delete(`/api/v1/documents/${docId}`);
 
121
  {uploading ? (
122
  <div className="space-y-2">
123
  <Loader2 className="w-5 h-5 mx-auto animate-spin text-primary" />
124
+ <p className="text-xs text-muted-foreground">{t("documents.uploading")}</p>
125
  <Progress value={uploadProgress} className="h-1" />
126
  </div>
127
  ) : (
128
  <>
129
  <Upload className="w-5 h-5 mx-auto text-muted-foreground mb-2" />
130
  <p className="text-xs text-muted-foreground">
131
+ {isDragActive ? t("documents.dropHere") : t("documents.dropOrClick")}
132
  </p>
133
  <p className="text-[10px] text-muted-foreground/60 mt-1">
134
+ {t("documents.uploadFormats")}
135
  </p>
136
  </>
137
  )}
 
141
  {/* ── Documents List ──────────────────────────── */}
142
  <div className="px-3 pt-3 pb-1">
143
  <h3 className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground mb-2">
144
+ {t("documents.documentsTitle", { count: documents.length })}
145
  </h3>
146
  </div>
147
 
 
149
  {documents.length === 0 ? (
150
  <div className="text-center py-12">
151
  <FolderOpen className="w-8 h-8 mx-auto text-muted-foreground/40 mb-3" />
152
+ <p className="text-sm text-muted-foreground">{t("documents.noDocuments")}</p>
153
+ <p className="text-xs text-muted-foreground/60 mt-1">{t("documents.getStarted")}</p>
154
  </div>
155
  ) : (
156
  <div className="space-y-1 pb-3">
 
178
  <>
179
  <span className="text-[10px] text-muted-foreground">•</span>
180
  <span className="text-[10px] text-muted-foreground">
181
+ {t("documents.pagesShort", { count: doc.page_count })}
182
  </span>
183
  <span className="text-[10px] text-muted-foreground">•</span>
184
  <span className="text-[10px] text-muted-foreground">
185
+ {t("documents.chunks", { count: doc.chunk_count })}
186
  </span>
187
  </>
188
  )}
189
  {doc.status === "processing" && (
190
  <Badge variant="secondary" className="text-[9px] h-4 px-1.5">
191
+ {t("documents.processing")}
192
  </Badge>
193
  )}
194
  {doc.status === "failed" && (
195
  <Badge variant="destructive" className="text-[9px] h-4 px-1.5">
196
+ {t("documents.failed")}
197
  </Badge>
198
  )}
199
  </div>
frontend/src/components/layout/Header.tsx CHANGED
@@ -1,6 +1,7 @@
1
  "use client";
2
 
3
  import { useAuth } from "@/lib/auth";
 
4
  import { useRouter } from "next/navigation";
5
  import { Button } from "@/components/ui/button";
6
  import { Avatar, AvatarFallback } from "@/components/ui/avatar";
@@ -32,6 +33,7 @@ interface HeaderProps {
32
 
33
  export default function Header({ sidebarOpen, onToggleSidebar, viewerOpen, onToggleViewer }: HeaderProps) {
34
  const { user, logout } = useAuth();
 
35
  const router = useRouter();
36
  const [isDark, setIsDark] = useState(true);
37
 
@@ -52,11 +54,24 @@ export default function Header({ sidebarOpen, onToggleSidebar, viewerOpen, onTog
52
  router.replace("/login");
53
  };
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  return (
56
  <header className="h-14 flex items-center justify-between px-4 border-b border-border/50 bg-card/50 backdrop-blur-md flex-shrink-0 z-50">
57
  {/* Left */}
58
  <div className="flex items-center gap-3">
59
- <Button variant="ghost" size="icon" className="h-8 w-8" onClick={onToggleSidebar} title={sidebarOpen ? "Close sidebar" : "Open sidebar"}>
60
  {sidebarOpen ? <PanelLeftClose className="w-4 h-4" /> : <PanelLeftOpen className="w-4 h-4" />}
61
  </Button>
62
 
@@ -64,20 +79,32 @@ export default function Header({ sidebarOpen, onToggleSidebar, viewerOpen, onTog
64
  <div className="w-7 h-7 rounded-lg bg-primary/15 flex items-center justify-center">
65
  <Brain className="w-4 h-4 text-primary" />
66
  </div>
67
- <span className="font-semibold text-sm hidden sm:inline">Document AI Analyst</span>
68
  </div>
69
  </div>
70
 
71
  {/* Right */}
72
  <div className="flex items-center gap-2">
73
- <Button variant="ghost" size="icon" className="h-8 w-8" onClick={onToggleViewer} title={viewerOpen ? "Close viewer" : "Open viewer"}>
74
  {viewerOpen ? <PanelRightClose className="w-4 h-4" /> : <PanelRightOpen className="w-4 h-4" />}
75
  </Button>
76
 
77
- <Button variant="ghost" size="icon" className="h-8 w-8" onClick={toggleTheme} title={isDark ? "Light mode" : "Dark mode"}>
78
  {isDark ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
79
  </Button>
80
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  <DropdownMenu>
82
  <DropdownMenuTrigger className="flex items-center h-8 gap-2 px-2 rounded-md hover:bg-accent transition-colors cursor-pointer">
83
  <Avatar className="w-6 h-6">
@@ -95,7 +122,7 @@ export default function Header({ sidebarOpen, onToggleSidebar, viewerOpen, onTog
95
  <DropdownMenuSeparator />
96
  <DropdownMenuItem className="text-destructive cursor-pointer" onClick={handleLogout}>
97
  <LogOut className="w-4 h-4 mr-2" />
98
- Sign out
99
  </DropdownMenuItem>
100
  </DropdownMenuContent>
101
  </DropdownMenu>
 
1
  "use client";
2
 
3
  import { useAuth } from "@/lib/auth";
4
+ import { useTranslation } from "react-i18next";
5
  import { useRouter } from "next/navigation";
6
  import { Button } from "@/components/ui/button";
7
  import { Avatar, AvatarFallback } from "@/components/ui/avatar";
 
33
 
34
  export default function Header({ sidebarOpen, onToggleSidebar, viewerOpen, onToggleViewer }: HeaderProps) {
35
  const { user, logout } = useAuth();
36
+ const { t, i18n } = useTranslation();
37
  const router = useRouter();
38
  const [isDark, setIsDark] = useState(true);
39
 
 
54
  router.replace("/login");
55
  };
56
 
57
+ const languageLabel = (language: string) => {
58
+ switch (language) {
59
+ case "hi":
60
+ return t("common.hindi");
61
+ case "es":
62
+ return t("common.spanish");
63
+ case "fr":
64
+ return t("common.french");
65
+ default:
66
+ return t("common.english");
67
+ }
68
+ };
69
+
70
  return (
71
  <header className="h-14 flex items-center justify-between px-4 border-b border-border/50 bg-card/50 backdrop-blur-md flex-shrink-0 z-50">
72
  {/* Left */}
73
  <div className="flex items-center gap-3">
74
+ <Button variant="ghost" size="icon" className="h-8 w-8" onClick={onToggleSidebar} title={sidebarOpen ? t("header.closeSidebar") : t("header.openSidebar")}>
75
  {sidebarOpen ? <PanelLeftClose className="w-4 h-4" /> : <PanelLeftOpen className="w-4 h-4" />}
76
  </Button>
77
 
 
79
  <div className="w-7 h-7 rounded-lg bg-primary/15 flex items-center justify-center">
80
  <Brain className="w-4 h-4 text-primary" />
81
  </div>
82
+ <span className="font-semibold text-sm hidden sm:inline">{t("common.appName")}</span>
83
  </div>
84
  </div>
85
 
86
  {/* Right */}
87
  <div className="flex items-center gap-2">
88
+ <Button variant="ghost" size="icon" className="h-8 w-8" onClick={onToggleViewer} title={viewerOpen ? t("header.closeViewer") : t("header.openViewer")}>
89
  {viewerOpen ? <PanelRightClose className="w-4 h-4" /> : <PanelRightOpen className="w-4 h-4" />}
90
  </Button>
91
 
92
+ <Button variant="ghost" size="icon" className="h-8 w-8" onClick={toggleTheme} title={isDark ? t("header.lightMode") : t("header.darkMode")}>
93
  {isDark ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
94
  </Button>
95
 
96
+ <select
97
+ aria-label={t("common.language")}
98
+ value={i18n.resolvedLanguage || "en"}
99
+ onChange={(e) => void i18n.changeLanguage(e.target.value)}
100
+ className="h-8 rounded-md border border-border bg-background px-2 text-xs text-foreground"
101
+ >
102
+ <option value="en">{languageLabel("en")}</option>
103
+ <option value="hi">{languageLabel("hi")}</option>
104
+ <option value="es">{languageLabel("es")}</option>
105
+ <option value="fr">{languageLabel("fr")}</option>
106
+ </select>
107
+
108
  <DropdownMenu>
109
  <DropdownMenuTrigger className="flex items-center h-8 gap-2 px-2 rounded-md hover:bg-accent transition-colors cursor-pointer">
110
  <Avatar className="w-6 h-6">
 
122
  <DropdownMenuSeparator />
123
  <DropdownMenuItem className="text-destructive cursor-pointer" onClick={handleLogout}>
124
  <LogOut className="w-4 h-4 mr-2" />
125
+ {t("header.signOut")}
126
  </DropdownMenuItem>
127
  </DropdownMenuContent>
128
  </DropdownMenu>
frontend/src/components/providers/I18nProvider.tsx ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+ import { I18nextProvider } from "react-i18next";
5
+ import i18n from "@/lib/i18n";
6
+
7
+ export default function I18nProvider({ children }: { children: React.ReactNode }) {
8
+ useEffect(() => {
9
+ document.documentElement.lang = i18n.resolvedLanguage || "en";
10
+
11
+ const handleLanguageChanged = (language: string) => {
12
+ document.documentElement.lang = language;
13
+ };
14
+
15
+ i18n.on("languageChanged", handleLanguageChanged);
16
+ return () => {
17
+ i18n.off("languageChanged", handleLanguageChanged);
18
+ };
19
+ }, []);
20
+
21
+ return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
22
+ }
frontend/src/lib/i18n.ts ADDED
@@ -0,0 +1,316 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import i18n from "i18next";
4
+ import LanguageDetector from "i18next-browser-languagedetector";
5
+ import { initReactI18next } from "react-i18next";
6
+
7
+ const resources = {
8
+ en: {
9
+ translation: {
10
+ common: {
11
+ appName: "Document AI Analyst",
12
+ language: "Language",
13
+ english: "English",
14
+ hindi: "Hindi",
15
+ spanish: "Spanish",
16
+ french: "French",
17
+ email: "Email",
18
+ password: "Password",
19
+ username: "Username",
20
+ },
21
+ header: {
22
+ closeSidebar: "Close sidebar",
23
+ openSidebar: "Open sidebar",
24
+ closeViewer: "Close viewer",
25
+ openViewer: "Open viewer",
26
+ lightMode: "Light mode",
27
+ darkMode: "Dark mode",
28
+ signOut: "Sign out",
29
+ },
30
+ login: {
31
+ title: "Welcome back",
32
+ description: "Sign in to your Document AI Analyst account",
33
+ fallbackError: "Login failed",
34
+ submit: "Sign In",
35
+ submitting: "Signing in...",
36
+ noAccount: "Don't have an account?",
37
+ createOne: "Create one",
38
+ },
39
+ register: {
40
+ title: "Create Account",
41
+ description: "Start analyzing documents with AI",
42
+ fallbackError: "Registration failed",
43
+ passwordPlaceholder: "Minimum 6 characters",
44
+ submit: "Create Account",
45
+ submitting: "Creating account...",
46
+ hasAccount: "Already have an account?",
47
+ signIn: "Sign in",
48
+ },
49
+ chat: {
50
+ askAboutDocument: "Ask about your document",
51
+ selectDocument: "Select a document",
52
+ readyPrompt: "\"{{name}}\" is ready. Ask any question and get cited answers.",
53
+ uploadPrompt: "Upload and select a document from the sidebar to start chatting.",
54
+ fallbackError: "Failed to get response: {{message}}",
55
+ clearConfirm: "Clear all chat history for this document?",
56
+ askPlaceholder: "Ask about \"{{name}}\"...",
57
+ selectPlaceholder: "Select a document first...",
58
+ exportTitle: "Export chat history",
59
+ markdown: "Markdown (.md)",
60
+ plainText: "Plain Text (.txt)",
61
+ },
62
+ documents: {
63
+ uploadFailed: "Upload failed",
64
+ deleteConfirm: "Delete this document and all its data?",
65
+ uploading: "Uploading...",
66
+ dropHere: "Drop files here",
67
+ dropOrClick: "Drop files or click to upload",
68
+ uploadFormats: "PDF, DOCX, TXT, MD (max 50MB)",
69
+ documentsTitle: "Documents ({{count}})",
70
+ noDocuments: "No documents yet",
71
+ getStarted: "Upload a file to get started",
72
+ pagesShort: "{{count}} pg",
73
+ chunks: "{{count}} chunks",
74
+ processing: "Processing",
75
+ failed: "Failed",
76
+ },
77
+ },
78
+ },
79
+ hi: {
80
+ translation: {
81
+ common: {
82
+ appName: "डॉक्यूमेंट एआई एनालिस्ट",
83
+ language: "भाषा",
84
+ english: "अंग्रेज़ी",
85
+ hindi: "हिंदी",
86
+ spanish: "स्पेनिश",
87
+ french: "फ़्रेंच",
88
+ email: "ईमेल",
89
+ password: "पासवर्ड",
90
+ username: "उपयोगकर्ता नाम",
91
+ },
92
+ header: {
93
+ closeSidebar: "साइडबार बंद करें",
94
+ openSidebar: "साइडबार खोलें",
95
+ closeViewer: "व्यूअर बंद करें",
96
+ openViewer: "व्यूअर खोलें",
97
+ lightMode: "लाइट मोड",
98
+ darkMode: "डार्क मोड",
99
+ signOut: "साइन आउट",
100
+ },
101
+ login: {
102
+ title: "वापसी पर स्वागत है",
103
+ description: "अपने डॉक्यूमेंट एआई एनालिस्ट खाते में साइन इन करें",
104
+ fallbackError: "लॉगिन विफल",
105
+ submit: "साइन इन करें",
106
+ submitting: "साइन इन हो रहा है...",
107
+ noAccount: "क्या आपका खाता नहीं है?",
108
+ createOne: "एक बनाएं",
109
+ },
110
+ register: {
111
+ title: "खाता बनाएं",
112
+ description: "एआई के साथ दस्तावेज़ों का विश्लेषण शुरू करें",
113
+ fallbackError: "पंजीकरण विफल",
114
+ passwordPlaceholder: "कम से कम 6 अक्षर",
115
+ submit: "खाता बनाएं",
116
+ submitting: "खाता बनाया जा रहा है...",
117
+ hasAccount: "क्या पहले से खाता है?",
118
+ signIn: "साइन इन करें",
119
+ },
120
+ chat: {
121
+ askAboutDocument: "अपने दस्तावेज़ के बारे में पूछें",
122
+ selectDocument: "एक दस्तावेज़ चुनें",
123
+ readyPrompt: "\"{{name}}\" ���ैयार है। कोई भी प्रश्न पूछें और स्रोत सहित उत्तर पाएं।",
124
+ uploadPrompt: "चैट शुरू करने के लिए साइडबार से फ़ाइल अपलोड और चुनें।",
125
+ fallbackError: "जवाब प्राप्त नहीं हुआ: {{message}}",
126
+ clearConfirm: "क्या इस दस्तावेज़ का पूरा चैट इतिहास साफ़ करें?",
127
+ askPlaceholder: "\"{{name}}\" के बारे में पूछें...",
128
+ selectPlaceholder: "पहले एक दस्तावेज़ चुनें...",
129
+ exportTitle: "चैट इतिहास निर्यात करें",
130
+ markdown: "मार्कडाउन (.md)",
131
+ plainText: "सादा पाठ (.txt)",
132
+ },
133
+ documents: {
134
+ uploadFailed: "अपलोड विफल",
135
+ deleteConfirm: "क्या इस दस्तावेज़ और उसके सभी डेटा को हटाएं?",
136
+ uploading: "अपलोड हो रहा है...",
137
+ dropHere: "फ़ाइलें यहाँ छोड़ें",
138
+ dropOrClick: "फ़ाइलें छोड़ें या अपलोड के लिए क्लिक करें",
139
+ uploadFormats: "PDF, DOCX, TXT, MD (अधिकतम 50MB)",
140
+ documentsTitle: "दस्तावेज़ ({{count}})",
141
+ noDocuments: "अभी तक कोई दस्तावेज़ नहीं",
142
+ getStarted: "शुरू करने के लिए फ़ाइल अपलोड करें",
143
+ pagesShort: "{{count}} पेज",
144
+ chunks: "{{count}} खंड",
145
+ processing: "प्रोसेस हो रहा है",
146
+ failed: "विफल",
147
+ },
148
+ },
149
+ },
150
+ es: {
151
+ translation: {
152
+ common: {
153
+ appName: "Analista IA de Documentos",
154
+ language: "Idioma",
155
+ english: "Inglés",
156
+ hindi: "Hindi",
157
+ spanish: "Español",
158
+ french: "Francés",
159
+ email: "Correo electrónico",
160
+ password: "Contraseña",
161
+ username: "Nombre de usuario",
162
+ },
163
+ header: {
164
+ closeSidebar: "Cerrar barra lateral",
165
+ openSidebar: "Abrir barra lateral",
166
+ closeViewer: "Cerrar visor",
167
+ openViewer: "Abrir visor",
168
+ lightMode: "Modo claro",
169
+ darkMode: "Modo oscuro",
170
+ signOut: "Cerrar sesión",
171
+ },
172
+ login: {
173
+ title: "Bienvenido de nuevo",
174
+ description: "Inicia sesión en tu cuenta de Analista IA de Documentos",
175
+ fallbackError: "Error al iniciar sesión",
176
+ submit: "Iniciar sesión",
177
+ submitting: "Iniciando sesión...",
178
+ noAccount: "¿No tienes una cuenta?",
179
+ createOne: "Crear una",
180
+ },
181
+ register: {
182
+ title: "Crear cuenta",
183
+ description: "Empieza a analizar documentos con IA",
184
+ fallbackError: "Error de registro",
185
+ passwordPlaceholder: "Mínimo 6 caracteres",
186
+ submit: "Crear cuenta",
187
+ submitting: "Creando cuenta...",
188
+ hasAccount: "¿Ya tienes una cuenta?",
189
+ signIn: "Inicia sesión",
190
+ },
191
+ chat: {
192
+ askAboutDocument: "Pregunta sobre tu documento",
193
+ selectDocument: "Selecciona un documento",
194
+ readyPrompt: "\"{{name}}\" está listo. Haz cualquier pregunta y obtén respuestas con citas.",
195
+ uploadPrompt: "Sube y selecciona un documento de la barra lateral para comenzar a chatear.",
196
+ fallbackError: "No se pudo obtener respuesta: {{message}}",
197
+ clearConfirm: "¿Borrar todo el historial de chat de este documento?",
198
+ askPlaceholder: "Pregunta sobre \"{{name}}\"...",
199
+ selectPlaceholder: "Primero selecciona un documento...",
200
+ exportTitle: "Exportar historial del chat",
201
+ markdown: "Markdown (.md)",
202
+ plainText: "Texto plano (.txt)",
203
+ },
204
+ documents: {
205
+ uploadFailed: "Error de carga",
206
+ deleteConfirm: "¿Eliminar este documento y todos sus datos?",
207
+ uploading: "Subiendo...",
208
+ dropHere: "Suelta archivos aquí",
209
+ dropOrClick: "Suelta archivos o haz clic para subir",
210
+ uploadFormats: "PDF, DOCX, TXT, MD (máx. 50MB)",
211
+ documentsTitle: "Documentos ({{count}})",
212
+ noDocuments: "Aún no hay documentos",
213
+ getStarted: "Sube un archivo para comenzar",
214
+ pagesShort: "{{count}} pág",
215
+ chunks: "{{count}} fragmentos",
216
+ processing: "Procesando",
217
+ failed: "Falló",
218
+ },
219
+ },
220
+ },
221
+ fr: {
222
+ translation: {
223
+ common: {
224
+ appName: "Analyste IA de Documents",
225
+ language: "Langue",
226
+ english: "Anglais",
227
+ hindi: "Hindi",
228
+ spanish: "Espagnol",
229
+ french: "Français",
230
+ email: "E-mail",
231
+ password: "Mot de passe",
232
+ username: "Nom d'utilisateur",
233
+ },
234
+ header: {
235
+ closeSidebar: "Fermer la barre latérale",
236
+ openSidebar: "Ouvrir la barre latérale",
237
+ closeViewer: "Fermer le lecteur",
238
+ openViewer: "Ouvrir le lecteur",
239
+ lightMode: "Mode clair",
240
+ darkMode: "Mode sombre",
241
+ signOut: "Se déconnecter",
242
+ },
243
+ login: {
244
+ title: "Bon retour",
245
+ description: "Connectez-vous à votre compte Analyste IA de Documents",
246
+ fallbackError: "Échec de la connexion",
247
+ submit: "Se connecter",
248
+ submitting: "Connexion en cours...",
249
+ noAccount: "Vous n'avez pas de compte ?",
250
+ createOne: "En créer un",
251
+ },
252
+ register: {
253
+ title: "Créer un compte",
254
+ description: "Commencez à analyser des documents avec l'IA",
255
+ fallbackError: "Échec de l'inscription",
256
+ passwordPlaceholder: "6 caractères minimum",
257
+ submit: "Créer un compte",
258
+ submitting: "Création du compte...",
259
+ hasAccount: "Vous avez déjà un compte ?",
260
+ signIn: "Se connecter",
261
+ },
262
+ chat: {
263
+ askAboutDocument: "Posez une question sur votre document",
264
+ selectDocument: "Sélectionnez un document",
265
+ readyPrompt: "\"{{name}}\" est prêt. Posez n'importe quelle question et obtenez des réponses sourcées.",
266
+ uploadPrompt: "Importez puis sélectionnez un document dans la barre latérale pour commencer à discuter.",
267
+ fallbackError: "Impossible d'obtenir une réponse : {{message}}",
268
+ clearConfirm: "Effacer tout l'historique de discussion de ce document ?",
269
+ askPlaceholder: "Posez une question sur \"{{name}}\"...",
270
+ selectPlaceholder: "Sélectionnez d'abord un document...",
271
+ exportTitle: "Exporter l'historique du chat",
272
+ markdown: "Markdown (.md)",
273
+ plainText: "Texte brut (.txt)",
274
+ },
275
+ documents: {
276
+ uploadFailed: "Échec de l'envoi",
277
+ deleteConfirm: "Supprimer ce document et toutes ses données ?",
278
+ uploading: "Envoi en cours...",
279
+ dropHere: "Déposez les fichiers ici",
280
+ dropOrClick: "Déposez les fichiers ou cliquez pour téléverser",
281
+ uploadFormats: "PDF, DOCX, TXT, MD (max 50 Mo)",
282
+ documentsTitle: "Documents ({{count}})",
283
+ noDocuments: "Aucun document pour le moment",
284
+ getStarted: "Importez un fichier pour commencer",
285
+ pagesShort: "{{count}} p",
286
+ chunks: "{{count}} segments",
287
+ processing: "Traitement",
288
+ failed: "Échec",
289
+ },
290
+ },
291
+ },
292
+ } as const;
293
+
294
+ if (!i18n.isInitialized) {
295
+ void i18n
296
+ .use(LanguageDetector)
297
+ .use(initReactI18next)
298
+ .init({
299
+ resources,
300
+ fallbackLng: "en",
301
+ supportedLngs: ["en", "hi", "es", "fr"],
302
+ interpolation: {
303
+ escapeValue: false,
304
+ },
305
+ detection: {
306
+ order: ["localStorage", "navigator"],
307
+ caches: ["localStorage"],
308
+ lookupLocalStorage: "i18nextLng",
309
+ },
310
+ react: {
311
+ useSuspense: false,
312
+ },
313
+ });
314
+ }
315
+
316
+ export default i18n;