Paramjit Singh commited on
Commit
ba887a7
·
unverified ·
2 Parent(s): 9ad7f7dd2bcbec

Merge pull request #173 from Jiya3177/feat/frontend-i18n-166

Browse files
frontend/package-lock.json CHANGED
@@ -11,6 +11,8 @@
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
  "next-themes": "^0.4.6",
@@ -18,6 +20,7 @@
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
  "rehype-highlight": "^7.0.2",
@@ -80,6 +83,7 @@
80
  "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
81
  "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
82
  "license": "MIT",
 
83
  "dependencies": {
84
  "@babel/code-frame": "^7.29.0",
85
  "@babel/generator": "^7.29.0",
@@ -673,6 +677,7 @@
673
  "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
674
  "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
675
  "license": "MIT",
 
676
  "engines": {
677
  "node": ">=12"
678
  },
@@ -2107,6 +2112,7 @@
2107
  "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
2108
  "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
2109
  "license": "MIT",
 
2110
  "engines": {
2111
  "node": "^14.21.3 || >=16"
2112
  },
@@ -2214,6 +2220,7 @@
2214
  "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==",
2215
  "devOptional": true,
2216
  "license": "Apache-2.0",
 
2217
  "dependencies": {
2218
  "playwright": "1.60.0"
2219
  },
@@ -2682,6 +2689,7 @@
2682
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz",
2683
  "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==",
2684
  "license": "MIT",
 
2685
  "dependencies": {
2686
  "undici-types": "~6.21.0"
2687
  }
@@ -2691,6 +2699,7 @@
2691
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
2692
  "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
2693
  "license": "MIT",
 
2694
  "dependencies": {
2695
  "csstype": "^3.2.2"
2696
  }
@@ -2777,6 +2786,7 @@
2777
  "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==",
2778
  "dev": true,
2779
  "license": "MIT",
 
2780
  "dependencies": {
2781
  "@typescript-eslint/scope-manager": "8.59.0",
2782
  "@typescript-eslint/types": "8.59.0",
@@ -3321,6 +3331,7 @@
3321
  "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
3322
  "dev": true,
3323
  "license": "MIT",
 
3324
  "bin": {
3325
  "acorn": "bin/acorn"
3326
  },
@@ -3775,6 +3786,7 @@
3775
  }
3776
  ],
3777
  "license": "MIT",
 
3778
  "dependencies": {
3779
  "baseline-browser-mapping": "^2.10.12",
3780
  "caniuse-lite": "^1.0.30001782",
@@ -4826,6 +4838,7 @@
4826
  "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
4827
  "dev": true,
4828
  "license": "MIT",
 
4829
  "dependencies": {
4830
  "@eslint-community/eslint-utils": "^4.8.0",
4831
  "@eslint-community/regexpp": "^4.12.1",
@@ -5011,6 +5024,7 @@
5011
  "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
5012
  "dev": true,
5013
  "license": "MIT",
 
5014
  "dependencies": {
5015
  "@rtsao/scc": "^1.1.0",
5016
  "array-includes": "^3.1.9",
@@ -5310,6 +5324,7 @@
5310
  "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
5311
  "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
5312
  "license": "MIT",
 
5313
  "dependencies": {
5314
  "accepts": "^2.0.0",
5315
  "body-parser": "^2.2.1",
@@ -5669,6 +5684,7 @@
5669
  "version": "2.3.2",
5670
  "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
5671
  "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
 
5672
  "hasInstallScript": true,
5673
  "license": "MIT",
5674
  "optional": true,
@@ -6133,10 +6149,20 @@
6133
  "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
6134
  "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==",
6135
  "license": "MIT",
 
6136
  "engines": {
6137
  "node": ">=16.9.0"
6138
  }
6139
  },
 
 
 
 
 
 
 
 
 
6140
  "node_modules/html-url-attributes": {
6141
  "version": "3.0.1",
6142
  "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
@@ -6189,6 +6215,44 @@
6189
  "node": ">=18.18.0"
6190
  }
6191
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6192
  "node_modules/iconv-lite": {
6193
  "version": "0.7.2",
6194
  "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
@@ -9527,6 +9591,7 @@
9527
  "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
9528
  "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
9529
  "license": "MIT",
 
9530
  "engines": {
9531
  "node": ">=0.10.0"
9532
  }
@@ -9536,6 +9601,7 @@
9536
  "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
9537
  "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
9538
  "license": "MIT",
 
9539
  "dependencies": {
9540
  "scheduler": "^0.27.0"
9541
  },
@@ -9560,6 +9626,33 @@
9560
  "react": ">= 16.8 || 18.0.0"
9561
  }
9562
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9563
  "node_modules/react-is": {
9564
  "version": "16.13.1",
9565
  "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -10833,6 +10926,7 @@
10833
  "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
10834
  "dev": true,
10835
  "license": "MIT",
 
10836
  "engines": {
10837
  "node": ">=12"
10838
  },
@@ -11101,6 +11195,7 @@
11101
  "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
11102
  "devOptional": true,
11103
  "license": "Apache-2.0",
 
11104
  "bin": {
11105
  "tsc": "bin/tsc",
11106
  "tsserver": "bin/tsserver"
@@ -11434,6 +11529,15 @@
11434
  "url": "https://opencollective.com/unified"
11435
  }
11436
  },
 
 
 
 
 
 
 
 
 
11437
  "node_modules/warning": {
11438
  "version": "4.0.3",
11439
  "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
@@ -11774,6 +11878,7 @@
11774
  "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
11775
  "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
11776
  "license": "MIT",
 
11777
  "funding": {
11778
  "url": "https://github.com/sponsors/colinhacks"
11779
  }
 
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
  "next-themes": "^0.4.6",
 
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
  "rehype-highlight": "^7.0.2",
 
83
  "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
84
  "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
85
  "license": "MIT",
86
+ "peer": true,
87
  "dependencies": {
88
  "@babel/code-frame": "^7.29.0",
89
  "@babel/generator": "^7.29.0",
 
677
  "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
678
  "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
679
  "license": "MIT",
680
+ "peer": true,
681
  "engines": {
682
  "node": ">=12"
683
  },
 
2112
  "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
2113
  "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
2114
  "license": "MIT",
2115
+ "peer": true,
2116
  "engines": {
2117
  "node": "^14.21.3 || >=16"
2118
  },
 
2220
  "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==",
2221
  "devOptional": true,
2222
  "license": "Apache-2.0",
2223
+ "peer": true,
2224
  "dependencies": {
2225
  "playwright": "1.60.0"
2226
  },
 
2689
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz",
2690
  "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==",
2691
  "license": "MIT",
2692
+ "peer": true,
2693
  "dependencies": {
2694
  "undici-types": "~6.21.0"
2695
  }
 
2699
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
2700
  "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
2701
  "license": "MIT",
2702
+ "peer": true,
2703
  "dependencies": {
2704
  "csstype": "^3.2.2"
2705
  }
 
2786
  "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==",
2787
  "dev": true,
2788
  "license": "MIT",
2789
+ "peer": true,
2790
  "dependencies": {
2791
  "@typescript-eslint/scope-manager": "8.59.0",
2792
  "@typescript-eslint/types": "8.59.0",
 
3331
  "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
3332
  "dev": true,
3333
  "license": "MIT",
3334
+ "peer": true,
3335
  "bin": {
3336
  "acorn": "bin/acorn"
3337
  },
 
3786
  }
3787
  ],
3788
  "license": "MIT",
3789
+ "peer": true,
3790
  "dependencies": {
3791
  "baseline-browser-mapping": "^2.10.12",
3792
  "caniuse-lite": "^1.0.30001782",
 
4838
  "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
4839
  "dev": true,
4840
  "license": "MIT",
4841
+ "peer": true,
4842
  "dependencies": {
4843
  "@eslint-community/eslint-utils": "^4.8.0",
4844
  "@eslint-community/regexpp": "^4.12.1",
 
5024
  "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
5025
  "dev": true,
5026
  "license": "MIT",
5027
+ "peer": true,
5028
  "dependencies": {
5029
  "@rtsao/scc": "^1.1.0",
5030
  "array-includes": "^3.1.9",
 
5324
  "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
5325
  "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
5326
  "license": "MIT",
5327
+ "peer": true,
5328
  "dependencies": {
5329
  "accepts": "^2.0.0",
5330
  "body-parser": "^2.2.1",
 
5684
  "version": "2.3.2",
5685
  "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
5686
  "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
5687
+ "dev": true,
5688
  "hasInstallScript": true,
5689
  "license": "MIT",
5690
  "optional": true,
 
6149
  "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
6150
  "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==",
6151
  "license": "MIT",
6152
+ "peer": true,
6153
  "engines": {
6154
  "node": ">=16.9.0"
6155
  }
6156
  },
6157
+ "node_modules/html-parse-stringify": {
6158
+ "version": "3.0.1",
6159
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
6160
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
6161
+ "license": "MIT",
6162
+ "dependencies": {
6163
+ "void-elements": "3.1.0"
6164
+ }
6165
+ },
6166
  "node_modules/html-url-attributes": {
6167
  "version": "3.0.1",
6168
  "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
 
6215
  "node": ">=18.18.0"
6216
  }
6217
  },
6218
+ "node_modules/i18next": {
6219
+ "version": "26.3.0",
6220
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.0.tgz",
6221
+ "integrity": "sha512-gHSgGpUXVmuqE2El1W61DmxeyeTlFfZgdJRWMo9jScAn5pu7TuTuiccb1zh3E2J9hEBVGJ23+96x0ieBhfuIHA==",
6222
+ "funding": [
6223
+ {
6224
+ "type": "individual",
6225
+ "url": "https://www.locize.com/i18next"
6226
+ },
6227
+ {
6228
+ "type": "individual",
6229
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
6230
+ },
6231
+ {
6232
+ "type": "individual",
6233
+ "url": "https://www.locize.com"
6234
+ }
6235
+ ],
6236
+ "license": "MIT",
6237
+ "peer": true,
6238
+ "peerDependencies": {
6239
+ "typescript": "^5 || ^6"
6240
+ },
6241
+ "peerDependenciesMeta": {
6242
+ "typescript": {
6243
+ "optional": true
6244
+ }
6245
+ }
6246
+ },
6247
+ "node_modules/i18next-browser-languagedetector": {
6248
+ "version": "8.2.1",
6249
+ "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz",
6250
+ "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==",
6251
+ "license": "MIT",
6252
+ "dependencies": {
6253
+ "@babel/runtime": "^7.23.2"
6254
+ }
6255
+ },
6256
  "node_modules/iconv-lite": {
6257
  "version": "0.7.2",
6258
  "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
 
9591
  "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
9592
  "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
9593
  "license": "MIT",
9594
+ "peer": true,
9595
  "engines": {
9596
  "node": ">=0.10.0"
9597
  }
 
9601
  "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
9602
  "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
9603
  "license": "MIT",
9604
+ "peer": true,
9605
  "dependencies": {
9606
  "scheduler": "^0.27.0"
9607
  },
 
9626
  "react": ">= 16.8 || 18.0.0"
9627
  }
9628
  },
9629
+ "node_modules/react-i18next": {
9630
+ "version": "17.0.8",
9631
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.8.tgz",
9632
+ "integrity": "sha512-0ooKbGLU8JXhe1zwpQUWIeXSgLPOfwJmgheWRIUpcoA0CpyabpGhayjdG+/eA5esC1AQ8h2jWpXjJfzQzeDOCw==",
9633
+ "license": "MIT",
9634
+ "dependencies": {
9635
+ "@babel/runtime": "^7.29.2",
9636
+ "html-parse-stringify": "^3.0.1",
9637
+ "use-sync-external-store": "^1.6.0"
9638
+ },
9639
+ "peerDependencies": {
9640
+ "i18next": ">= 26.2.0",
9641
+ "react": ">= 16.8.0",
9642
+ "typescript": "^5 || ^6"
9643
+ },
9644
+ "peerDependenciesMeta": {
9645
+ "react-dom": {
9646
+ "optional": true
9647
+ },
9648
+ "react-native": {
9649
+ "optional": true
9650
+ },
9651
+ "typescript": {
9652
+ "optional": true
9653
+ }
9654
+ }
9655
+ },
9656
  "node_modules/react-is": {
9657
  "version": "16.13.1",
9658
  "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
 
10926
  "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
10927
  "dev": true,
10928
  "license": "MIT",
10929
+ "peer": true,
10930
  "engines": {
10931
  "node": ">=12"
10932
  },
 
11195
  "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
11196
  "devOptional": true,
11197
  "license": "Apache-2.0",
11198
+ "peer": true,
11199
  "bin": {
11200
  "tsc": "bin/tsc",
11201
  "tsserver": "bin/tsserver"
 
11529
  "url": "https://opencollective.com/unified"
11530
  }
11531
  },
11532
+ "node_modules/void-elements": {
11533
+ "version": "3.1.0",
11534
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
11535
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
11536
+ "license": "MIT",
11537
+ "engines": {
11538
+ "node": ">=0.10.0"
11539
+ }
11540
+ },
11541
  "node_modules/warning": {
11542
  "version": "4.0.3",
11543
  "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
 
11878
  "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
11879
  "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
11880
  "license": "MIT",
11881
+ "peer": true,
11882
  "funding": {
11883
  "url": "https://github.com/sponsors/colinhacks"
11884
  }
frontend/package.json CHANGED
@@ -14,6 +14,8 @@
14
  "@base-ui/react": "^1.4.1",
15
  "class-variance-authority": "^0.7.1",
16
  "clsx": "^2.1.1",
 
 
17
  "lucide-react": "^1.8.0",
18
  "next": "16.2.4",
19
  "next-themes": "^0.4.6",
@@ -21,6 +23,7 @@
21
  "react": "19.2.4",
22
  "react-dom": "19.2.4",
23
  "react-dropzone": "^15.0.0",
 
24
  "react-markdown": "^10.1.0",
25
  "react-pdf": "^10.4.1",
26
  "rehype-highlight": "^7.0.2",
 
14
  "@base-ui/react": "^1.4.1",
15
  "class-variance-authority": "^0.7.1",
16
  "clsx": "^2.1.1",
17
+ "i18next": "^26.3.0",
18
+ "i18next-browser-languagedetector": "^8.2.1",
19
  "lucide-react": "^1.8.0",
20
  "next": "16.2.4",
21
  "next-themes": "^0.4.6",
 
23
  "react": "19.2.4",
24
  "react-dom": "19.2.4",
25
  "react-dropzone": "^15.0.0",
26
+ "react-i18next": "^17.0.8",
27
  "react-markdown": "^10.1.0",
28
  "react-pdf": "^10.4.1",
29
  "rehype-highlight": "^7.0.2",
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
  import { ThemeProvider } from "@/components/layout/ThemeProvider";
7
 
8
  const inter = Inter({
@@ -33,12 +34,12 @@ export default function RootLayout({
33
  disableTransitionOnChange
34
  >
35
  <AuthProvider>
36
- <TooltipProvider>
37
- {children}
38
- </TooltipProvider>
39
  </AuthProvider>
40
  </ThemeProvider>
41
  </body>
42
  </html>
43
  );
44
- }
 
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
  import { ThemeProvider } from "@/components/layout/ThemeProvider";
8
 
9
  const inter = Inter({
 
34
  disableTransitionOnChange
35
  >
36
  <AuthProvider>
37
+ <I18nProvider>
38
+ <TooltipProvider>{children}</TooltipProvider>
39
+ </I18nProvider>
40
  </AuthProvider>
41
  </ThemeProvider>
42
  </body>
43
  </html>
44
  );
45
+ }
frontend/src/app/login/page.tsx CHANGED
@@ -3,6 +3,7 @@
3
  import { useCallback, 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";
@@ -12,6 +13,7 @@ import GoogleSignInButton from "@/components/auth/GoogleSignInButton";
12
 
13
  export default function LoginPage() {
14
  const { login } = useAuth();
 
15
  const router = useRouter();
16
  const [email, setEmail] = useState("");
17
  const [password, setPassword] = useState("");
@@ -32,7 +34,7 @@ export default function LoginPage() {
32
  await login(email, password);
33
  router.replace("/dashboard");
34
  } catch (err: unknown) {
35
- const message = err instanceof Error ? err.message : "Login failed";
36
  setError(message);
37
  } finally {
38
  setLoading(false);
@@ -51,8 +53,8 @@ export default function LoginPage() {
51
  <Brain className="w-6 h-6 text-primary" />
52
  </div>
53
  </div>
54
- <CardTitle className="text-2xl font-bold">Welcome back</CardTitle>
55
- <CardDescription>Sign in to your Document AI Analyst account</CardDescription>
56
  </CardHeader>
57
 
58
  <CardContent>
@@ -71,7 +73,7 @@ export default function LoginPage() {
71
  )}
72
 
73
  <div className="space-y-2">
74
- <label className="text-sm font-medium">Email</label>
75
  <Input
76
  id="login-email"
77
  type="email"
@@ -84,7 +86,7 @@ export default function LoginPage() {
84
  </div>
85
 
86
  <div className="space-y-2">
87
- <label className="text-sm font-medium">Password</label>
88
  <div className="relative">
89
  <Input
90
  id="login-password"
@@ -109,18 +111,18 @@ export default function LoginPage() {
109
  {loading ? (
110
  <span className="flex items-center gap-2">
111
  <span className="w-4 h-4 border-2 border-primary-foreground/30 border-t-primary-foreground rounded-full animate-spin" />
112
- Signing in...
113
  </span>
114
  ) : (
115
- "Sign In"
116
  )}
117
  </Button>
118
  </form>
119
 
120
  <p className="text-center text-sm text-muted-foreground mt-6">
121
- Don&apos;t have an account?{" "}
122
  <Link href="/register" className="text-primary hover:underline font-medium">
123
- Create one
124
  </Link>
125
  </p>
126
  </CardContent>
 
3
  import { useCallback, 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";
 
13
 
14
  export default function LoginPage() {
15
  const { login } = useAuth();
16
+ const { t } = useTranslation();
17
  const router = useRouter();
18
  const [email, setEmail] = useState("");
19
  const [password, setPassword] = useState("");
 
34
  await login(email, password);
35
  router.replace("/dashboard");
36
  } catch (err: unknown) {
37
+ const message = err instanceof Error ? err.message : t("login.fallbackError");
38
  setError(message);
39
  } finally {
40
  setLoading(false);
 
53
  <Brain className="w-6 h-6 text-primary" />
54
  </div>
55
  </div>
56
+ <CardTitle className="text-2xl font-bold">{t("login.title")}</CardTitle>
57
+ <CardDescription>{t("login.description")}</CardDescription>
58
  </CardHeader>
59
 
60
  <CardContent>
 
73
  )}
74
 
75
  <div className="space-y-2">
76
+ <label className="text-sm font-medium">{t("common.email")}</label>
77
  <Input
78
  id="login-email"
79
  type="email"
 
86
  </div>
87
 
88
  <div className="space-y-2">
89
+ <label className="text-sm font-medium">{t("common.password")}</label>
90
  <div className="relative">
91
  <Input
92
  id="login-password"
 
111
  {loading ? (
112
  <span className="flex items-center gap-2">
113
  <span className="w-4 h-4 border-2 border-primary-foreground/30 border-t-primary-foreground rounded-full animate-spin" />
114
+ {t("login.submitting")}
115
  </span>
116
  ) : (
117
+ t("login.submit")
118
  )}
119
  </Button>
120
  </form>
121
 
122
  <p className="text-center text-sm text-muted-foreground mt-6">
123
+ {t("login.noAccount")}{" "}
124
  <Link href="/register" className="text-primary hover:underline font-medium">
125
+ {t("login.createOne")}
126
  </Link>
127
  </p>
128
  </CardContent>
frontend/src/app/register/page.tsx CHANGED
@@ -3,6 +3,7 @@
3
  import { useCallback, 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";
@@ -12,6 +13,7 @@ import GoogleSignInButton from "@/components/auth/GoogleSignInButton";
12
 
13
  export default function RegisterPage() {
14
  const { register } = useAuth();
 
15
  const router = useRouter();
16
  const [username, setUsername] = useState("");
17
  const [email, setEmail] = useState("");
@@ -33,7 +35,7 @@ export default function RegisterPage() {
33
  await register(username, email, password);
34
  router.replace("/dashboard");
35
  } catch (err: unknown) {
36
- const message = err instanceof Error ? err.message : "Registration failed";
37
  setError(message);
38
  } finally {
39
  setLoading(false);
@@ -51,8 +53,8 @@ export default function RegisterPage() {
51
  <Brain className="w-6 h-6 text-primary" />
52
  </div>
53
  </div>
54
- <CardTitle className="text-2xl font-bold">Create Account</CardTitle>
55
- <CardDescription>Start analyzing documents with AI</CardDescription>
56
  </CardHeader>
57
 
58
  <CardContent>
@@ -71,7 +73,7 @@ export default function RegisterPage() {
71
  )}
72
 
73
  <div className="space-y-2">
74
- <label className="text-sm font-medium">Username</label>
75
  <Input
76
  id="reg-username"
77
  type="text"
@@ -85,7 +87,7 @@ export default function RegisterPage() {
85
  </div>
86
 
87
  <div className="space-y-2">
88
- <label className="text-sm font-medium">Email</label>
89
  <Input
90
  id="reg-email"
91
  type="email"
@@ -98,12 +100,12 @@ export default function RegisterPage() {
98
  </div>
99
 
100
  <div className="space-y-2">
101
- <label className="text-sm font-medium">Password</label>
102
  <div className="relative">
103
  <Input
104
  id="reg-password"
105
  type={showPw ? "text" : "password"}
106
- placeholder="Minimum 6 characters"
107
  value={password}
108
  onChange={(e) => setPassword(e.target.value)}
109
  required
@@ -124,18 +126,18 @@ export default function RegisterPage() {
124
  {loading ? (
125
  <span className="flex items-center gap-2">
126
  <span className="w-4 h-4 border-2 border-primary-foreground/30 border-t-primary-foreground rounded-full animate-spin" />
127
- Creating account...
128
  </span>
129
  ) : (
130
- "Create Account"
131
  )}
132
  </Button>
133
  </form>
134
 
135
  <p className="text-center text-sm text-muted-foreground mt-6">
136
- Already have an account?{" "}
137
  <Link href="/login" className="text-primary hover:underline font-medium">
138
- Sign in
139
  </Link>
140
  </p>
141
  </CardContent>
 
3
  import { useCallback, 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";
 
13
 
14
  export default function RegisterPage() {
15
  const { register } = useAuth();
16
+ const { t } = useTranslation();
17
  const router = useRouter();
18
  const [username, setUsername] = useState("");
19
  const [email, setEmail] = useState("");
 
35
  await register(username, email, password);
36
  router.replace("/dashboard");
37
  } catch (err: unknown) {
38
+ const message = err instanceof Error ? err.message : t("register.fallbackError");
39
  setError(message);
40
  } finally {
41
  setLoading(false);
 
53
  <Brain className="w-6 h-6 text-primary" />
54
  </div>
55
  </div>
56
+ <CardTitle className="text-2xl font-bold">{t("register.title")}</CardTitle>
57
+ <CardDescription>{t("register.description")}</CardDescription>
58
  </CardHeader>
59
 
60
  <CardContent>
 
73
  )}
74
 
75
  <div className="space-y-2">
76
+ <label className="text-sm font-medium">{t("common.username")}</label>
77
  <Input
78
  id="reg-username"
79
  type="text"
 
87
  </div>
88
 
89
  <div className="space-y-2">
90
+ <label className="text-sm font-medium">{t("common.email")}</label>
91
  <Input
92
  id="reg-email"
93
  type="email"
 
100
  </div>
101
 
102
  <div className="space-y-2">
103
+ <label className="text-sm font-medium">{t("common.password")}</label>
104
  <div className="relative">
105
  <Input
106
  id="reg-password"
107
  type={showPw ? "text" : "password"}
108
+ placeholder={t("register.passwordPlaceholder")}
109
  value={password}
110
  onChange={(e) => setPassword(e.target.value)}
111
  required
 
126
  {loading ? (
127
  <span className="flex items-center gap-2">
128
  <span className="w-4 h-4 border-2 border-primary-foreground/30 border-t-primary-foreground rounded-full animate-spin" />
129
+ {t("register.submitting")}
130
  </span>
131
  ) : (
132
+ t("register.submit")
133
  )}
134
  </Button>
135
  </form>
136
 
137
  <p className="text-center text-sm text-muted-foreground mt-6">
138
+ {t("register.hasAccount")}{" "}
139
  <Link href="/login" className="text-primary hover:underline font-medium">
140
+ {t("register.signIn")}
141
  </Link>
142
  </p>
143
  </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 { useChatStore, type ChatMsg, type SourceChunk } from "@/store/chat-store";
@@ -16,6 +17,7 @@ interface Props {
16
  }
17
 
18
  export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
 
19
  const messages = useChatStore((state) => state.messages);
20
  const input = useChatStore((state) => state.input);
21
  const streaming = useChatStore((state) => state.streaming);
@@ -185,7 +187,9 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
185
  m.id === assistantId
186
  ? {
187
  ...m,
188
- content: `Failed to get response: ${err instanceof Error ? err.message : "Unknown error"}`,
 
 
189
  isStreaming: false,
190
  }
191
  : m
@@ -198,7 +202,7 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
198
  };
199
 
200
  const handleClear = async () => {
201
- if (!activeDoc || !confirm("Clear all chat history for this document?")) return;
202
  try {
203
  await api.delete(`/api/v1/chat/history/${activeDoc.id}`);
204
  setMessages([]);
@@ -250,12 +254,12 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
250
  <MessageSquare className="w-8 h-8 text-primary/60" />
251
  </div>
252
  <h3 className="text-lg font-semibold mb-1">
253
- {activeDoc ? "Ask about your document" : "Select a document"}
254
  </h3>
255
  <p className="text-sm text-muted-foreground text-center max-w-sm">
256
  {activeDoc
257
- ? `"${activeDoc.original_name}" is ready. Ask any question and get cited answers.`
258
- : "Upload and select a document from the sidebar to start chatting."}
259
  </p>
260
  </div>
261
  ) : (
@@ -293,8 +297,8 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
293
  onKeyDown={handleKeyDown}
294
  placeholder={
295
  activeDoc
296
- ? `Ask about "${activeDoc.original_name}"...`
297
- : "Select a document first..."
298
  }
299
  disabled={streaming}
300
  className="min-h-[44px] max-h-32 resize-none bg-background/50 border-border/50"
@@ -324,7 +328,7 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
324
  size="icon"
325
  onClick={() => setShowExportMenu((v) => !v)}
326
  className="h-[44px] w-[44px] text-muted-foreground hover:text-primary"
327
- title="Export chat history"
328
  >
329
  <Download className="w-4 h-4" />
330
  </Button>
@@ -336,7 +340,7 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
336
  className="w-full flex items-center gap-2 rounded-md px-3 py-2 text-sm hover:bg-accent transition-colors text-left"
337
  >
338
  <span className="text-base">📝</span>
339
- Markdown (.md)
340
  </button>
341
  <button
342
  id="export-txt-btn"
@@ -344,7 +348,7 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
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
- Plain Text (.txt)
348
  </button>
349
  <button
350
  id="export-pdf-btn"
@@ -352,7 +356,7 @@ export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
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
- PDF (.pdf)
356
  </button>
357
  </div>
358
  )}
 
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 { useChatStore, type ChatMsg, type SourceChunk } from "@/store/chat-store";
 
17
  }
18
 
19
  export default function ChatPanel({ activeDoc, onCitationClick }: Props) {
20
+ const { t } = useTranslation();
21
  const messages = useChatStore((state) => state.messages);
22
  const input = useChatStore((state) => state.input);
23
  const streaming = useChatStore((state) => state.streaming);
 
187
  m.id === assistantId
188
  ? {
189
  ...m,
190
+ content: t("chat.fallbackError", {
191
+ message: err instanceof Error ? err.message : "Unknown error",
192
+ }),
193
  isStreaming: false,
194
  }
195
  : m
 
202
  };
203
 
204
  const handleClear = async () => {
205
+ if (!activeDoc || !confirm(t("chat.clearConfirm"))) return;
206
  try {
207
  await api.delete(`/api/v1/chat/history/${activeDoc.id}`);
208
  setMessages([]);
 
254
  <MessageSquare className="w-8 h-8 text-primary/60" />
255
  </div>
256
  <h3 className="text-lg font-semibold mb-1">
257
+ {activeDoc ? t("chat.askAboutDocument") : t("chat.selectDocument")}
258
  </h3>
259
  <p className="text-sm text-muted-foreground text-center max-w-sm">
260
  {activeDoc
261
+ ? t("chat.readyPrompt", { name: activeDoc.original_name })
262
+ : t("chat.uploadPrompt")}
263
  </p>
264
  </div>
265
  ) : (
 
297
  onKeyDown={handleKeyDown}
298
  placeholder={
299
  activeDoc
300
+ ? t("chat.askPlaceholder", { name: activeDoc.original_name })
301
+ : t("chat.selectPlaceholder")
302
  }
303
  disabled={streaming}
304
  className="min-h-[44px] max-h-32 resize-none bg-background/50 border-border/50"
 
328
  size="icon"
329
  onClick={() => setShowExportMenu((v) => !v)}
330
  className="h-[44px] w-[44px] text-muted-foreground hover:text-primary"
331
+ title={t("chat.exportTitle")}
332
  >
333
  <Download className="w-4 h-4" />
334
  </Button>
 
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
+ {t("chat.markdown")}
344
  </button>
345
  <button
346
  id="export-txt-btn"
 
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
+ {t("chat.plainText")}
352
  </button>
353
  <button
354
  id="export-pdf-btn"
 
356
  className="w-full flex items-center gap-2 rounded-md px-3 py-2 text-sm hover:bg-accent transition-colors text-left"
357
  >
358
  <span className="text-base">📕</span>
359
+ {t("chat.pdf")}
360
  </button>
361
  </div>
362
  )}
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">
@@ -179,22 +181,22 @@ export default function DocumentSidebar({ documents = [], activeDoc, onSelectDoc
179
  <>
180
  <span className="text-[10px] text-muted-foreground">•</span>
181
  <span className="text-[10px] text-muted-foreground">
182
- {doc.page_count} pg
183
  </span>
184
  <span className="text-[10px] text-muted-foreground">•</span>
185
  <span className="text-[10px] text-muted-foreground">
186
- {doc.chunk_count} chunks
187
  </span>
188
  </>
189
  )}
190
  {doc.status === "processing" && (
191
  <Badge variant="secondary" className="text-[9px] h-4 px-1.5">
192
- Processing
193
  </Badge>
194
  )}
195
  {doc.status === "failed" && (
196
  <Badge variant="destructive" className="text-[9px] h-4 px-1.5">
197
- Failed
198
  </Badge>
199
  )}
200
  </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">
 
181
  <>
182
  <span className="text-[10px] text-muted-foreground">•</span>
183
  <span className="text-[10px] text-muted-foreground">
184
+ {t("documents.pagesShort", { count: doc.page_count })}
185
  </span>
186
  <span className="text-[10px] text-muted-foreground">•</span>
187
  <span className="text-[10px] text-muted-foreground">
188
+ {t("documents.chunks", { count: doc.chunk_count })}
189
  </span>
190
  </>
191
  )}
192
  {doc.status === "processing" && (
193
  <Badge variant="secondary" className="text-[9px] h-4 px-1.5">
194
+ {t("documents.processing")}
195
  </Badge>
196
  )}
197
  {doc.status === "failed" && (
198
  <Badge variant="destructive" className="text-[9px] h-4 px-1.5">
199
+ {t("documents.failed")}
200
  </Badge>
201
  )}
202
  </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";
@@ -39,6 +40,7 @@ const getServerSnapshot = () => false;
39
 
40
  export default function Header({ sidebarOpen, onToggleSidebar, viewerOpen, onToggleViewer }: HeaderProps) {
41
  const { user, logout } = useAuth();
 
42
  const router = useRouter();
43
  const { theme, setTheme } = useTheme();
44
  const mounted = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); // ← replaces useState + useEffect
@@ -51,11 +53,24 @@ export default function Header({ sidebarOpen, onToggleSidebar, viewerOpen, onTog
51
  router.replace("/login");
52
  };
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  return (
55
  <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">
56
  {/* Left */}
57
  <div className="flex items-center gap-3">
58
- <Button variant="ghost" size="icon" className="h-8 w-8" onClick={onToggleSidebar} title={sidebarOpen ? "Close sidebar" : "Open sidebar"}>
59
  {sidebarOpen ? <PanelLeftClose className="w-4 h-4" /> : <PanelLeftOpen className="w-4 h-4" />}
60
  </Button>
61
 
@@ -63,22 +78,34 @@ export default function Header({ sidebarOpen, onToggleSidebar, viewerOpen, onTog
63
  <div className="w-7 h-7 rounded-lg bg-primary/15 flex items-center justify-center">
64
  <Brain className="w-4 h-4 text-primary" />
65
  </div>
66
- <span className="font-semibold text-sm hidden sm:inline">Document AI Analyst</span>
67
  </div>
68
  </div>
69
 
70
  {/* Right */}
71
  <div className="flex items-center gap-2">
72
- <Button variant="ghost" size="icon" className="h-8 w-8" onClick={onToggleViewer} title={viewerOpen ? "Close viewer" : "Open viewer"}>
73
  {viewerOpen ? <PanelRightClose className="w-4 h-4" /> : <PanelRightOpen className="w-4 h-4" />}
74
  </Button>
75
 
76
  {mounted && (
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
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  <DropdownMenu>
83
  <DropdownMenuTrigger
84
  render={
@@ -103,7 +130,7 @@ export default function Header({ sidebarOpen, onToggleSidebar, viewerOpen, onTog
103
  <DropdownMenuSeparator />
104
  <DropdownMenuItem className="text-destructive cursor-pointer" onClick={handleLogout}>
105
  <LogOut className="w-4 h-4 mr-2" />
106
- Sign out
107
  </DropdownMenuItem>
108
  </DropdownMenuContent>
109
  </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";
 
40
 
41
  export default function Header({ sidebarOpen, onToggleSidebar, viewerOpen, onToggleViewer }: HeaderProps) {
42
  const { user, logout } = useAuth();
43
+ const { t, i18n } = useTranslation();
44
  const router = useRouter();
45
  const { theme, setTheme } = useTheme();
46
  const mounted = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); // ← replaces useState + useEffect
 
53
  router.replace("/login");
54
  };
55
 
56
+ const languageLabel = (language: string) => {
57
+ switch (language) {
58
+ case "hi":
59
+ return t("common.hindi");
60
+ case "es":
61
+ return t("common.spanish");
62
+ case "fr":
63
+ return t("common.french");
64
+ default:
65
+ return t("common.english");
66
+ }
67
+ };
68
+
69
  return (
70
  <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">
71
  {/* Left */}
72
  <div className="flex items-center gap-3">
73
+ <Button variant="ghost" size="icon" className="h-8 w-8" onClick={onToggleSidebar} title={sidebarOpen ? t("header.closeSidebar") : t("header.openSidebar")}>
74
  {sidebarOpen ? <PanelLeftClose className="w-4 h-4" /> : <PanelLeftOpen className="w-4 h-4" />}
75
  </Button>
76
 
 
78
  <div className="w-7 h-7 rounded-lg bg-primary/15 flex items-center justify-center">
79
  <Brain className="w-4 h-4 text-primary" />
80
  </div>
81
+ <span className="font-semibold text-sm hidden sm:inline">{t("common.appName")}</span>
82
  </div>
83
  </div>
84
 
85
  {/* Right */}
86
  <div className="flex items-center gap-2">
87
+ <Button variant="ghost" size="icon" className="h-8 w-8" onClick={onToggleViewer} title={viewerOpen ? t("header.closeViewer") : t("header.openViewer")}>
88
  {viewerOpen ? <PanelRightClose className="w-4 h-4" /> : <PanelRightOpen className="w-4 h-4" />}
89
  </Button>
90
 
91
  {mounted && (
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
 
97
+ <select
98
+ aria-label={t("common.language")}
99
+ value={i18n.resolvedLanguage || "en"}
100
+ onChange={(e) => void i18n.changeLanguage(e.target.value)}
101
+ className="h-8 rounded-md border border-border bg-background px-2 text-xs text-foreground"
102
+ >
103
+ <option value="en">{languageLabel("en")}</option>
104
+ <option value="hi">{languageLabel("hi")}</option>
105
+ <option value="es">{languageLabel("es")}</option>
106
+ <option value="fr">{languageLabel("fr")}</option>
107
+ </select>
108
+
109
  <DropdownMenu>
110
  <DropdownMenuTrigger
111
  render={
 
130
  <DropdownMenuSeparator />
131
  <DropdownMenuItem className="text-destructive cursor-pointer" onClick={handleLogout}>
132
  <LogOut className="w-4 h-4 mr-2" />
133
+ {t("header.signOut")}
134
  </DropdownMenuItem>
135
  </DropdownMenuContent>
136
  </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,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ pdf: "PDF (.pdf)",
62
+ },
63
+ documents: {
64
+ uploadFailed: "Upload failed",
65
+ deleteConfirm: "Delete this document and all its data?",
66
+ uploading: "Uploading...",
67
+ dropHere: "Drop files here",
68
+ dropOrClick: "Drop files or click to upload",
69
+ uploadFormats: "PDF, DOCX, TXT, MD (max 50MB)",
70
+ documentsTitle: "Documents ({{count}})",
71
+ noDocuments: "No documents yet",
72
+ getStarted: "Upload a file to get started",
73
+ pagesShort: "{{count}} pg",
74
+ chunks: "{{count}} chunks",
75
+ processing: "Processing",
76
+ failed: "Failed",
77
+ },
78
+ },
79
+ },
80
+ hi: {
81
+ translation: {
82
+ common: {
83
+ appName: "डॉक्यूमेंट एआई एनालिस्ट",
84
+ language: "भाषा",
85
+ english: "अंग्रेज़ी",
86
+ hindi: "हिंदी",
87
+ spanish: "स्पेनिश",
88
+ french: "फ़्रेंच",
89
+ email: "ईमेल",
90
+ password: "पासवर्ड",
91
+ username: "उपयोगकर्ता नाम",
92
+ },
93
+ header: {
94
+ closeSidebar: "साइडबार बंद करें",
95
+ openSidebar: "साइडबार खोलें",
96
+ closeViewer: "व्यूअर बंद करें",
97
+ openViewer: "व्यूअर खोलें",
98
+ lightMode: "लाइट मोड",
99
+ darkMode: "डार्क मोड",
100
+ signOut: "साइन आउट",
101
+ },
102
+ login: {
103
+ title: "वापसी पर स्वागत है",
104
+ description: "अपने डॉक्यूमेंट एआई एनालिस्ट खाते में साइन इन करें",
105
+ fallbackError: "लॉगिन विफल",
106
+ submit: "साइन इन करें",
107
+ submitting: "साइन इन हो रहा है...",
108
+ noAccount: "क्या आपका खाता नहीं है?",
109
+ createOne: "एक बनाएं",
110
+ },
111
+ register: {
112
+ title: "खाता बनाएं",
113
+ description: "एआई के साथ दस्तावेज़ों का विश्लेषण शुरू करें",
114
+ fallbackError: "पंजीकरण विफल",
115
+ passwordPlaceholder: "कम से कम 6 अक्षर",
116
+ submit: "खाता बनाएं",
117
+ submitting: "खाता बनाया जा रहा है...",
118
+ hasAccount: "क्या पहले से खाता है?",
119
+ signIn: "साइन इन करें",
120
+ },
121
+ chat: {
122
+ askAboutDocument: "अपने दस्तावेज़ के बारे में पूछें",
123
+ selectDocument: "एक दस्तावेज़ चुनें",
124
+ readyPrompt: "\"{{name}}\" तैयार है। कोई भी प्रश्न पूछें और स्रोत सहित उत्तर पाएं।",
125
+ uploadPrompt: "चैट शुरू करने के लिए साइडबार से फ़ाइल अपलोड और चुनें।",
126
+ fallbackError: "जवाब प्राप्त नहीं हुआ: {{message}}",
127
+ clearConfirm: "क्या इस दस्तावेज़ का पूरा चैट इतिहास साफ़ करें?",
128
+ askPlaceholder: "\"{{name}}\" के बारे में पूछें...",
129
+ selectPlaceholder: "पहले एक दस्तावेज़ चुनें...",
130
+ exportTitle: "चैट इतिहास निर्यात करें",
131
+ markdown: "मार्कडाउन (.md)",
132
+ plainText: "सादा पाठ (.txt)",
133
+ pdf: "पीडीएफ (.pdf)",
134
+ },
135
+ documents: {
136
+ uploadFailed: "अपलोड विफल",
137
+ deleteConfirm: "क्या इस दस्तावेज़ और उसके सभी डेटा को हटाएं?",
138
+ uploading: "अपलोड हो रहा है...",
139
+ dropHere: "फ़ाइलें यहाँ छोड़ें",
140
+ dropOrClick: "फ़ाइलें छोड़ें या अपलोड के लिए क्लिक करें",
141
+ uploadFormats: "PDF, DOCX, TXT, MD (अधिकतम 50MB)",
142
+ documentsTitle: "दस्तावेज़ ({{count}})",
143
+ noDocuments: "अभी तक कोई दस्तावेज़ नहीं",
144
+ getStarted: "शुरू करने के लिए फ़ाइल अपलोड करें",
145
+ pagesShort: "{{count}} पेज",
146
+ chunks: "{{count}} खंड",
147
+ processing: "प्रोसेस हो रहा है",
148
+ failed: "विफल",
149
+ },
150
+ },
151
+ },
152
+ es: {
153
+ translation: {
154
+ common: {
155
+ appName: "Analista IA de Documentos",
156
+ language: "Idioma",
157
+ english: "Inglés",
158
+ hindi: "Hindi",
159
+ spanish: "Español",
160
+ french: "Francés",
161
+ email: "Correo electrónico",
162
+ password: "Contraseña",
163
+ username: "Nombre de usuario",
164
+ },
165
+ header: {
166
+ closeSidebar: "Cerrar barra lateral",
167
+ openSidebar: "Abrir barra lateral",
168
+ closeViewer: "Cerrar visor",
169
+ openViewer: "Abrir visor",
170
+ lightMode: "Modo claro",
171
+ darkMode: "Modo oscuro",
172
+ signOut: "Cerrar sesión",
173
+ },
174
+ login: {
175
+ title: "Bienvenido de nuevo",
176
+ description: "Inicia sesión en tu cuenta de Analista IA de Documentos",
177
+ fallbackError: "Error al iniciar sesión",
178
+ submit: "Iniciar sesión",
179
+ submitting: "Iniciando sesión...",
180
+ noAccount: "¿No tienes una cuenta?",
181
+ createOne: "Crear una",
182
+ },
183
+ register: {
184
+ title: "Crear cuenta",
185
+ description: "Empieza a analizar documentos con IA",
186
+ fallbackError: "Error de registro",
187
+ passwordPlaceholder: "Mínimo 6 caracteres",
188
+ submit: "Crear cuenta",
189
+ submitting: "Creando cuenta...",
190
+ hasAccount: "¿Ya tienes una cuenta?",
191
+ signIn: "Inicia sesión",
192
+ },
193
+ chat: {
194
+ askAboutDocument: "Pregunta sobre tu documento",
195
+ selectDocument: "Selecciona un documento",
196
+ readyPrompt: "\"{{name}}\" está listo. Haz cualquier pregunta y obtén respuestas con citas.",
197
+ uploadPrompt: "Sube y selecciona un documento de la barra lateral para comenzar a chatear.",
198
+ fallbackError: "No se pudo obtener respuesta: {{message}}",
199
+ clearConfirm: "¿Borrar todo el historial de chat de este documento?",
200
+ askPlaceholder: "Pregunta sobre \"{{name}}\"...",
201
+ selectPlaceholder: "Primero selecciona un documento...",
202
+ exportTitle: "Exportar historial del chat",
203
+ markdown: "Markdown (.md)",
204
+ plainText: "Texto plano (.txt)",
205
+ pdf: "PDF (.pdf)",
206
+ },
207
+ documents: {
208
+ uploadFailed: "Error de carga",
209
+ deleteConfirm: "¿Eliminar este documento y todos sus datos?",
210
+ uploading: "Subiendo...",
211
+ dropHere: "Suelta archivos aquí",
212
+ dropOrClick: "Suelta archivos o haz clic para subir",
213
+ uploadFormats: "PDF, DOCX, TXT, MD (máx. 50MB)",
214
+ documentsTitle: "Documentos ({{count}})",
215
+ noDocuments: "Aún no hay documentos",
216
+ getStarted: "Sube un archivo para comenzar",
217
+ pagesShort: "{{count}} pág",
218
+ chunks: "{{count}} fragmentos",
219
+ processing: "Procesando",
220
+ failed: "Falló",
221
+ },
222
+ },
223
+ },
224
+ fr: {
225
+ translation: {
226
+ common: {
227
+ appName: "Analyste IA de Documents",
228
+ language: "Langue",
229
+ english: "Anglais",
230
+ hindi: "Hindi",
231
+ spanish: "Espagnol",
232
+ french: "Français",
233
+ email: "E-mail",
234
+ password: "Mot de passe",
235
+ username: "Nom d'utilisateur",
236
+ },
237
+ header: {
238
+ closeSidebar: "Fermer la barre latérale",
239
+ openSidebar: "Ouvrir la barre latérale",
240
+ closeViewer: "Fermer le lecteur",
241
+ openViewer: "Ouvrir le lecteur",
242
+ lightMode: "Mode clair",
243
+ darkMode: "Mode sombre",
244
+ signOut: "Se déconnecter",
245
+ },
246
+ login: {
247
+ title: "Bon retour",
248
+ description: "Connectez-vous à votre compte Analyste IA de Documents",
249
+ fallbackError: "Échec de la connexion",
250
+ submit: "Se connecter",
251
+ submitting: "Connexion en cours...",
252
+ noAccount: "Vous n'avez pas de compte ?",
253
+ createOne: "En créer un",
254
+ },
255
+ register: {
256
+ title: "Créer un compte",
257
+ description: "Commencez à analyser des documents avec l'IA",
258
+ fallbackError: "Échec de l'inscription",
259
+ passwordPlaceholder: "6 caractères minimum",
260
+ submit: "Créer un compte",
261
+ submitting: "Création du compte...",
262
+ hasAccount: "Vous avez déjà un compte ?",
263
+ signIn: "Se connecter",
264
+ },
265
+ chat: {
266
+ askAboutDocument: "Posez une question sur votre document",
267
+ selectDocument: "Sélectionnez un document",
268
+ readyPrompt: "\"{{name}}\" est prêt. Posez n'importe quelle question et obtenez des réponses sourcées.",
269
+ uploadPrompt: "Importez puis sélectionnez un document dans la barre latérale pour commencer à discuter.",
270
+ fallbackError: "Impossible d'obtenir une réponse : {{message}}",
271
+ clearConfirm: "Effacer tout l'historique de discussion de ce document ?",
272
+ askPlaceholder: "Posez une question sur \"{{name}}\"...",
273
+ selectPlaceholder: "Sélectionnez d'abord un document...",
274
+ exportTitle: "Exporter l'historique du chat",
275
+ markdown: "Markdown (.md)",
276
+ plainText: "Texte brut (.txt)",
277
+ pdf: "PDF (.pdf)",
278
+ },
279
+ documents: {
280
+ uploadFailed: "Échec de l'envoi",
281
+ deleteConfirm: "Supprimer ce document et toutes ses données ?",
282
+ uploading: "Envoi en cours...",
283
+ dropHere: "Déposez les fichiers ici",
284
+ dropOrClick: "Déposez les fichiers ou cliquez pour téléverser",
285
+ uploadFormats: "PDF, DOCX, TXT, MD (max 50 Mo)",
286
+ documentsTitle: "Documents ({{count}})",
287
+ noDocuments: "Aucun document pour le moment",
288
+ getStarted: "Importez un fichier pour commencer",
289
+ pagesShort: "{{count}} p",
290
+ chunks: "{{count}} segments",
291
+ processing: "Traitement",
292
+ failed: "Échec",
293
+ },
294
+ },
295
+ },
296
+ } as const;
297
+
298
+ if (!i18n.isInitialized) {
299
+ void i18n
300
+ .use(LanguageDetector)
301
+ .use(initReactI18next)
302
+ .init({
303
+ resources,
304
+ fallbackLng: "en",
305
+ supportedLngs: ["en", "hi", "es", "fr"],
306
+ interpolation: {
307
+ escapeValue: false,
308
+ },
309
+ detection: {
310
+ order: ["localStorage", "navigator"],
311
+ caches: ["localStorage"],
312
+ lookupLocalStorage: "i18nextLng",
313
+ },
314
+ react: {
315
+ useSuspense: false,
316
+ },
317
+ });
318
+ }
319
+
320
+ export default i18n;