Compare commits

...

1 Commits

Author SHA1 Message Date
Eli Bosley
0b080501c1 feat: initial commit 2025-09-09 09:19:53 -04:00
12 changed files with 489 additions and 2 deletions

View File

@@ -114,6 +114,7 @@
"graphql-subscriptions": "3.0.0",
"graphql-tag": "2.12.6",
"graphql-ws": "6.0.6",
"i18next": "^25.5.2",
"ini": "5.0.0",
"ip": "2.0.1",
"jose": "6.0.13",
@@ -123,6 +124,7 @@
"mustache": "4.2.0",
"nest-authz": "2.17.0",
"nest-commander": "3.19.0",
"nestjs-i18n": "^10.5.1",
"nestjs-pino": "4.4.0",
"node-cache": "5.1.2",
"node-window-polyfill": "1.0.4",

View File

@@ -0,0 +1,29 @@
{
"hello": "Hello",
"welcome": "Welcome to Unraid API",
"server": {
"started": "Server started successfully",
"stopped": "Server stopped",
"error": "Server error occurred"
},
"auth": {
"unauthorized": "Unauthorized access",
"forbidden": "Access forbidden",
"invalidToken": "Invalid authentication token",
"tokenExpired": "Authentication token expired",
"loginSuccess": "Login successful",
"logoutSuccess": "Logout successful"
},
"docker": {
"containerStarted": "Container {{name}} started",
"containerStopped": "Container {{name}} stopped",
"containerRemoved": "Container {{name}} removed",
"imageDeleted": "Image {{name}} deleted"
},
"vm": {
"started": "Virtual machine {{name}} started",
"stopped": "Virtual machine {{name}} stopped",
"paused": "Virtual machine {{name}} paused",
"resumed": "Virtual machine {{name}} resumed"
}
}

View File

@@ -0,0 +1,38 @@
{
"notFound": "Resource not found",
"internalError": "Internal server error",
"badRequest": "Bad request",
"validation": {
"required": "{{field}} is required",
"invalid": "{{field}} is invalid",
"minLength": "{{field}} must be at least {{min}} characters",
"maxLength": "{{field}} must not exceed {{max}} characters",
"email": "Invalid email format",
"numeric": "{{field}} must be a number",
"range": "{{field}} must be between {{min}} and {{max}}"
},
"docker": {
"containerNotFound": "Container {{id}} not found",
"imageNotFound": "Image {{id}} not found",
"networkNotFound": "Network {{id}} not found",
"volumeNotFound": "Volume {{id}} not found",
"operationFailed": "Docker operation failed: {{error}}"
},
"vm": {
"notFound": "Virtual machine {{name}} not found",
"invalidState": "Invalid VM state for operation",
"operationFailed": "VM operation failed: {{error}}"
},
"plugin": {
"notFound": "Plugin {{name}} not found",
"installFailed": "Failed to install plugin {{name}}",
"uninstallFailed": "Failed to uninstall plugin {{name}}",
"invalidManifest": "Invalid plugin manifest"
},
"file": {
"notFound": "File not found: {{path}}",
"accessDenied": "Access denied: {{path}}",
"readError": "Failed to read file: {{path}}",
"writeError": "Failed to write file: {{path}}"
}
}

View File

@@ -0,0 +1,20 @@
{
"isNotEmpty": "{{property}} should not be empty",
"isEmail": "{{property}} must be a valid email",
"isString": "{{property}} must be a string",
"isNumber": "{{property}} must be a number",
"isBoolean": "{{property}} must be a boolean",
"isArray": "{{property}} must be an array",
"isObject": "{{property}} must be an object",
"isEnum": "{{property}} must be one of: {{values}}",
"minLength": "{{property}} must be at least {{min}} characters",
"maxLength": "{{property}} must not exceed {{max}} characters",
"min": "{{property}} must be at least {{min}}",
"max": "{{property}} must not exceed {{max}}",
"matches": "{{property}} format is invalid",
"isUUID": "{{property}} must be a valid UUID",
"isURL": "{{property}} must be a valid URL",
"isIP": "{{property}} must be a valid IP address",
"isPort": "{{property}} must be a valid port number",
"isPath": "{{property}} must be a valid path"
}

View File

@@ -0,0 +1,28 @@
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { HeaderResolver, I18nModule, QueryResolver, AcceptLanguageResolver } from 'nestjs-i18n';
import * as path from 'path';
@Module({
imports: [
I18nModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
fallbackLanguage: 'en',
loaderOptions: {
path: path.join(__dirname),
watch: configService.get('NODE_ENV') === 'development',
},
resolvers: [
new QueryResolver(['lang', 'locale', 'l']),
new HeaderResolver(['x-locale', 'x-lang']),
new AcceptLanguageResolver(),
],
typesOutputPath: path.join(__dirname, '../../src/generated/i18n.generated.ts'),
}),
}),
],
exports: [I18nModule],
})
export class AppI18nModule {}

View File

@@ -0,0 +1,36 @@
import { Injectable } from '@nestjs/common';
import { I18nService, I18nContext } from 'nestjs-i18n';
@Injectable()
export class ExampleI18nService {
constructor(private readonly i18n: I18nService) {}
// Basic translation
getWelcomeMessage(lang?: string): string {
return this.i18n.translate('common.welcome', { lang });
}
// Translation with interpolation
getContainerStartedMessage(containerName: string, lang?: string): string {
return this.i18n.translate('common.docker.containerStarted', {
args: { name: containerName },
lang,
});
}
// Using context from request
async getErrorMessage(errorKey: string): Promise<string> {
const context = I18nContext.current();
return this.i18n.translate(`errors.${errorKey}`, {
lang: context?.lang,
});
}
// Validation message with parameters
getValidationMessage(field: string, min: number, max: number, lang?: string): string {
return this.i18n.translate('errors.validation.range', {
args: { field, min, max },
lang,
});
}
}

View File

@@ -9,6 +9,7 @@ import { LoggerModule } from 'nestjs-pino';
import { apiLogger } from '@app/core/log.js';
import { LOG_LEVEL } from '@app/environment.js';
import { AppI18nModule } from '@app/i18n/i18n.module.js';
import { PubSubModule } from '@app/unraid-api/app/pubsub.module.js';
import { AuthModule } from '@app/unraid-api/auth/auth.module.js';
import { AuthenticationGuard } from '@app/unraid-api/auth/authentication.guard.js';
@@ -23,6 +24,7 @@ import { UnraidFileModifierModule } from '@app/unraid-api/unraid-file-modifier/u
imports: [
GlobalDepsModule,
LegacyConfigModule,
AppI18nModule,
PubSubModule,
ScheduleModule.forRoot(),
LoggerModule.forRoot({

35
crowdin.yml Normal file
View File

@@ -0,0 +1,35 @@
project_id_env: CROWDIN_PROJECT_ID
api_token_env: CROWDIN_API_TOKEN
base_path: .
preserve_hierarchy: true
files:
# Frontend translations
- source: /web/src/locales/en_US.json
translation: /web/src/locales/%locale%.json
type: json
update_option: update_as_unapproved
skip_untranslated_strings: false
export_only_approved: false
# Backend translations
- source: /api/src/i18n/en/common.json
translation: /api/src/i18n/%two_letters_code%/%file_name%.json
type: json
update_option: update_as_unapproved
skip_untranslated_strings: false
export_only_approved: false
- source: /api/src/i18n/en/errors.json
translation: /api/src/i18n/%two_letters_code%/%file_name%.json
type: json
update_option: update_as_unapproved
skip_untranslated_strings: false
export_only_approved: false
- source: /api/src/i18n/en/validation.json
translation: /api/src/i18n/%two_letters_code%/%file_name%.json
type: json
update_option: update_as_unapproved
skip_untranslated_strings: false
export_only_approved: false

111
i18n-setup.md Normal file
View File

@@ -0,0 +1,111 @@
# i18n Setup Guide
## Overview
This project uses:
- **Frontend**: `vue-i18n` for Vue.js components
- **Backend**: `nestjs-i18n` for NestJS API
- **Translation Management**: Crowdin for collaborative translation
## Project Structure
```
/web/src/locales/ # Frontend translations
en_US.json # Base English translations
*.json # Other locale translations
/api/src/i18n/ # Backend translations
en/ # English translations
common.json # Common messages
errors.json # Error messages
validation.json # Validation messages
*/ # Other locales
```
## Environment Setup
Set these environment variables for Crowdin:
```bash
export CROWDIN_PROJECT_ID=your_project_id
export CROWDIN_API_TOKEN=your_api_token
```
## Usage
### Frontend (Vue)
```vue
<script setup>
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
// Use translation
const message = t('welcome')
</script>
<template>
<div>{{ t('hello') }}</div>
</template>
```
### Backend (NestJS)
```typescript
import { Injectable } from '@nestjs/common';
import { I18nService } from 'nestjs-i18n';
@Injectable()
export class MyService {
constructor(private readonly i18n: I18nService) {}
async getMessage() {
return this.i18n.translate('common.welcome');
}
}
```
## Commands
### Extract Translation Keys
```bash
# Extract missing keys from Vue components
pnpm i18n:extract
# Check for missing translations
pnpm --filter ./web i18n:missing
```
### Crowdin Sync
```bash
# Upload source files to Crowdin
pnpm crowdin:upload
# Download translations from Crowdin
pnpm crowdin:download
# Full sync (extract, upload, download)
pnpm crowdin:sync
```
## Build-time Integration
Translations are bundled at build time:
- Frontend: Included in the Vite build process
- Backend: Copied with the NestJS build
## Adding New Translations
1. Add key to source files (`en_US.json` or `en/*.json`)
2. Run `pnpm crowdin:upload` to sync with Crowdin
3. Translators work on Crowdin
4. Run `pnpm crowdin:download` to fetch translations
5. Build and deploy
## Locale Detection
- **Frontend**: Uses browser's Accept-Language header
- **Backend**: Detects from (in order):
1. Query parameter: `?lang=fr`
2. Header: `x-locale` or `x-lang`
3. Accept-Language header

View File

@@ -16,7 +16,11 @@
"check": "manypkg check",
"sync-webgui-repo": "node web/scripts/sync-webgui-repo.js",
"preinstall": "npx check-node-version --node 22 || echo '❌ Node.js 22 required. See readme.md Prerequisites section.'",
"postinstall": "simple-git-hooks"
"postinstall": "simple-git-hooks",
"i18n:extract": "pnpm --filter ./web i18n:extract",
"crowdin:upload": "crowdin upload sources",
"crowdin:download": "crowdin download",
"crowdin:sync": "pnpm i18n:extract && crowdin upload sources && crowdin download"
},
"pnpm": {
"overrides": {
@@ -56,6 +60,7 @@
"ignore": "7.0.5"
},
"devDependencies": {
"@crowdin/cli": "^4.11.0",
"lint-staged": "16.1.5",
"simple-git-hooks": "2.13.1"
},

177
pnpm-lock.yaml generated
View File

@@ -25,6 +25,9 @@ importers:
specifier: 7.0.5
version: 7.0.5
devDependencies:
'@crowdin/cli':
specifier: ^4.11.0
version: 4.11.0
lint-staged:
specifier: 16.1.5
version: 16.1.5
@@ -223,6 +226,9 @@ importers:
graphql-ws:
specifier: 6.0.6
version: 6.0.6(crossws@0.3.5)(graphql@16.11.0)(ws@8.18.3)
i18next:
specifier: ^25.5.2
version: 25.5.2(typescript@5.9.2)
ini:
specifier: 5.0.0
version: 5.0.0
@@ -250,6 +256,9 @@ importers:
nest-commander:
specifier: 3.19.0
version: 3.19.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2))(@types/inquirer@9.0.9)(@types/node@22.18.0)(typescript@5.9.2)
nestjs-i18n:
specifier: ^10.5.1
version: 10.5.1(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-validator@0.14.2)(rxjs@7.8.2)
nestjs-pino:
specifier: 4.4.0
version: 4.4.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(pino-http@10.5.0)(pino@9.9.0)(rxjs@7.8.2)
@@ -1311,6 +1320,9 @@ importers:
vue-eslint-parser:
specifier: 10.2.0
version: 10.2.0(eslint@9.34.0(jiti@2.5.1))
vue-i18n-extract:
specifier: github:Spittal/vue-i18n-extract
version: https://codeload.github.com/Spittal/vue-i18n-extract/tar.gz/f856484d2f62abbd931d84676519f529062d94da
vue-tsc:
specifier: 3.0.6
version: 3.0.6(typescript@5.9.2)
@@ -1901,6 +1913,10 @@ packages:
conventional-commits-parser:
optional: true
'@crowdin/cli@4.11.0':
resolution: {integrity: sha512-oIOzCHCc9eHOqcQw1bjcnfFUoJDFVobCN5MEZ1rwjYOhPMpIcRNBDnUT/b43LcRWsogU3c0BwcWN/BHvV19DWg==}
hasBin: true
'@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
@@ -4969,6 +4985,9 @@ packages:
abstract-logging@2.0.1:
resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
accept-language-parser@1.5.0:
resolution: {integrity: sha512-QhyTbMLYo0BBGg1aWbeMG4ekWtds/31BrEU+DONOg/7ax23vxpL03Pb7/zBmha2v7vdD3AyzZVWBVGEZxKOXWw==}
accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
@@ -5371,6 +5390,9 @@ packages:
bser@2.1.1:
resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
buffer-crc32@0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@@ -5656,6 +5678,10 @@ packages:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
command-exists-promise@2.0.2:
resolution: {integrity: sha512-T6PB6vdFrwnHXg/I0kivM3DqaCGZLjjYSOe0a5WgFKcz1sOnmOeIjnhQPXVXX3QjVbLyTJ85lJkX6lUpukTzaA==}
engines: {node: '>=6'}
command-exists@1.2.9:
resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==}
@@ -5681,6 +5707,10 @@ packages:
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
commander@6.2.1:
resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==}
engines: {node: '>= 6'}
commander@9.5.0:
resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
engines: {node: ^12.20.0 || >=14}
@@ -6229,6 +6259,10 @@ packages:
dot-case@3.0.4:
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
dot-object@2.1.5:
resolution: {integrity: sha512-xHF8EP4XH/Ba9fvAF2LDd5O3IITVolerVV6xvkxoM8zlGEiCUrggpAnHyOoKJKCrhvPcGATFAUwIujj7bRG5UA==}
hasBin: true
dot-prop@5.3.0:
resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
engines: {node: '>=8'}
@@ -7229,6 +7263,11 @@ packages:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
glob@8.1.0:
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
engines: {node: '>=12'}
deprecated: Glob versions prior to v9 are no longer supported
global-agent@3.0.0:
resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
engines: {node: '>=10.0'}
@@ -7503,6 +7542,14 @@ packages:
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
engines: {node: '>=18.18.0'}
i18next@25.5.2:
resolution: {integrity: sha512-lW8Zeh37i/o0zVr+NoCHfNnfvVw+M6FQbRp36ZZ/NyHDJ3NJVpp2HhAUyU9WafL5AssymNoOjMRB48mmx2P6Hw==}
peerDependencies:
typescript: ^5
peerDependenciesMeta:
typescript:
optional: true
iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
@@ -7603,6 +7650,10 @@ packages:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
interpret@1.4.0:
resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
engines: {node: '>= 0.10'}
invariant@2.2.4:
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
@@ -7849,6 +7900,10 @@ packages:
is-utf8@0.2.1:
resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==}
is-valid-glob@1.0.0:
resolution: {integrity: sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==}
engines: {node: '>=0.10.0'}
is-weakmap@2.0.2:
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
engines: {node: '>= 0.4'}
@@ -8493,6 +8548,10 @@ packages:
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
minimatch@5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
minimatch@7.4.6:
resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==}
engines: {node: '>=10'}
@@ -8646,6 +8705,15 @@ packages:
'@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
'@types/inquirer': ^8.1.3
nestjs-i18n@10.5.1:
resolution: {integrity: sha512-cJJFz+RUfav23QACpGCq1pdXNLYC3tBesrP14RGoE/YYcD4xosQPX2eyjvDNuo0Ti63Xtn6j57wDNEUKrZqmSw==}
engines: {node: '>=18'}
peerDependencies:
'@nestjs/common': '*'
'@nestjs/core': '*'
class-validator: '*'
rxjs: '*'
nestjs-pino@4.4.0:
resolution: {integrity: sha512-+GMNlcNWDRrMtlQftfcxN+5pV2C25A4wsYIY7cfRJTMW4b8IFKYReDrG1lUp5LGql9fXemmnVJ2Ww10iIkCZPQ==}
engines: {node: '>= 14'}
@@ -9099,6 +9167,9 @@ packages:
pause@0.0.1:
resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
perfect-debounce@1.0.0:
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
@@ -9524,6 +9595,10 @@ packages:
resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
engines: {node: '>= 4'}
rechoir@0.6.2:
resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
engines: {node: '>= 0.10'}
redent@3.0.0:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'}
@@ -9872,6 +9947,11 @@ packages:
resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
engines: {node: '>= 0.4'}
shelljs@0.8.5:
resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==}
engines: {node: '>=4'}
hasBin: true
shimmer@1.2.1:
resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==}
@@ -10071,6 +10151,9 @@ packages:
string-env-interpolation@1.0.1:
resolution: {integrity: sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg==}
string-format@2.0.0:
resolution: {integrity: sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==}
string-width@3.1.0:
resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==}
engines: {node: '>=6'}
@@ -11017,6 +11100,11 @@ packages:
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
vue-i18n-extract@https://codeload.github.com/Spittal/vue-i18n-extract/tar.gz/f856484d2f62abbd931d84676519f529062d94da:
resolution: {tarball: https://codeload.github.com/Spittal/vue-i18n-extract/tar.gz/f856484d2f62abbd931d84676519f529062d94da}
version: 2.0.8
hasBin: true
vue-i18n@11.1.11:
resolution: {integrity: sha512-LvyteQoXeQiuILbzqv13LbyBna/TEv2Ha+4ZWK2AwGHUzZ8+IBaZS0TJkCgn5izSPLcgZwXy9yyTrewCb2u/MA==}
engines: {node: '>= 16'}
@@ -11352,6 +11440,10 @@ packages:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
yauzl@3.2.0:
resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==}
engines: {node: '>=12'}
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@@ -12169,6 +12261,16 @@ snapshots:
conventional-commits-filter: 5.0.0
conventional-commits-parser: 6.0.0
'@crowdin/cli@4.11.0':
dependencies:
command-exists-promise: 2.0.2
node-fetch: 2.7.0
shelljs: 0.8.5
tar: 7.4.3
yauzl: 3.2.0
transitivePeerDependencies:
- encoding
'@cspotcode/source-map-support@0.8.1':
dependencies:
'@jridgewell/trace-mapping': 0.3.9
@@ -15692,6 +15794,8 @@ snapshots:
abstract-logging@2.0.1: {}
accept-language-parser@1.5.0: {}
accepts@1.3.8:
dependencies:
mime-types: 2.1.35
@@ -16114,6 +16218,8 @@ snapshots:
dependencies:
node-int64: 0.4.0
buffer-crc32@0.2.13: {}
buffer-from@1.1.2: {}
buffer@5.7.1:
@@ -16456,6 +16562,8 @@ snapshots:
dependencies:
delayed-stream: 1.0.0
command-exists-promise@2.0.2: {}
command-exists@1.2.9: {}
commander@10.0.1: {}
@@ -16470,6 +16578,8 @@ snapshots:
commander@2.20.3: {}
commander@6.2.1: {}
commander@9.5.0:
optional: true
@@ -16998,6 +17108,11 @@ snapshots:
no-case: 3.0.4
tslib: 2.8.1
dot-object@2.1.5:
dependencies:
commander: 6.2.1
glob: 7.2.3
dot-prop@5.3.0:
dependencies:
is-obj: 2.0.0
@@ -18232,6 +18347,14 @@ snapshots:
once: 1.4.0
path-is-absolute: 1.0.1
glob@8.1.0:
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 5.1.6
once: 1.4.0
global-agent@3.0.0:
dependencies:
boolean: 3.2.0
@@ -18520,6 +18643,12 @@ snapshots:
human-signals@8.0.1: {}
i18next@25.5.2(typescript@5.9.2):
dependencies:
'@babel/runtime': 7.27.6
optionalDependencies:
typescript: 5.9.2
iconv-lite@0.4.24:
dependencies:
safer-buffer: 2.1.2
@@ -18644,6 +18773,8 @@ snapshots:
hasown: 2.0.2
side-channel: 1.1.0
interpret@1.4.0: {}
invariant@2.2.4:
dependencies:
loose-envify: 1.4.0
@@ -18867,6 +18998,8 @@ snapshots:
is-utf8@0.2.1: {}
is-valid-glob@1.0.0: {}
is-weakmap@2.0.2: {}
is-weakref@1.1.1:
@@ -19468,6 +19601,10 @@ snapshots:
dependencies:
brace-expansion: 1.1.12
minimatch@5.1.6:
dependencies:
brace-expansion: 2.0.2
minimatch@7.4.6:
dependencies:
brace-expansion: 2.0.2
@@ -19599,6 +19736,19 @@ snapshots:
- '@types/node'
- typescript
nestjs-i18n@10.5.1(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-validator@0.14.2)(rxjs@7.8.2):
dependencies:
'@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2)
'@nestjs/core': 11.1.6(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2)
accept-language-parser: 1.5.0
chokidar: 3.6.0
class-validator: 0.14.2
cookie: 0.7.1
iterare: 1.2.1
js-yaml: 4.1.0
rxjs: 7.8.2
string-format: 2.0.0
nestjs-pino@4.4.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(pino-http@10.5.0)(pino@9.9.0)(rxjs@7.8.2):
dependencies:
'@nestjs/common': 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2)
@@ -20088,6 +20238,8 @@ snapshots:
pause@0.0.1: {}
pend@1.2.0: {}
perfect-debounce@1.0.0: {}
picocolors@1.1.1: {}
@@ -20590,6 +20742,10 @@ snapshots:
tiny-invariant: 1.3.3
tslib: 2.8.1
rechoir@0.6.2:
dependencies:
resolve: 1.22.10
redent@3.0.0:
dependencies:
indent-string: 4.0.0
@@ -21034,6 +21190,12 @@ snapshots:
shell-quote@1.8.3: {}
shelljs@0.8.5:
dependencies:
glob: 7.2.3
interpret: 1.4.0
rechoir: 0.6.2
shimmer@1.2.1: {}
side-channel-list@1.0.0:
@@ -21244,6 +21406,8 @@ snapshots:
string-env-interpolation@1.0.1: {}
string-format@2.0.0: {}
string-width@3.1.0:
dependencies:
emoji-regex: 7.0.3
@@ -22275,6 +22439,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
vue-i18n-extract@https://codeload.github.com/Spittal/vue-i18n-extract/tar.gz/f856484d2f62abbd931d84676519f529062d94da:
dependencies:
cac: 6.7.14
dot-object: 2.1.5
glob: 8.1.0
is-valid-glob: 1.0.0
js-yaml: 4.1.0
vue-i18n@11.1.11(vue@3.5.20(typescript@5.9.2)):
dependencies:
'@intlify/core-base': 11.1.11
@@ -22630,6 +22802,11 @@ snapshots:
y18n: 5.0.8
yargs-parser: 21.1.1
yauzl@3.2.0:
dependencies:
buffer-crc32: 0.2.13
pend: 1.2.0
yocto-queue@0.1.0: {}
yocto-queue@1.2.1: {}

View File

@@ -34,7 +34,10 @@
"test": "vitest run",
"test:watch": "vitest",
"test:ci": "vitest run",
"test:standalone": "pnpm run build && vite --config vite.test.config.ts"
"test:standalone": "pnpm run build && vite --config vite.test.config.ts",
"// i18n": "",
"i18n:extract": "vue-i18n-extract report --vueFiles './src/**/*.{vue,ts,js}' --languageFiles './src/locales/*.json'",
"i18n:missing": "vue-i18n-extract report --vueFiles './src/**/*.{vue,ts,js}' --languageFiles './src/locales/*.json' --output missing.json && cat missing.json"
},
"devDependencies": {
"@eslint/js": "9.34.0",
@@ -84,6 +87,7 @@
"vitest": "3.2.4",
"vue": "3.5.20",
"vue-eslint-parser": "10.2.0",
"vue-i18n-extract": "github:Spittal/vue-i18n-extract",
"vue-tsc": "3.0.6"
},
"dependencies": {