@drone1/alt 0.9.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/test.yml +30 -0
- package/.mocharc.cjs +13 -0
- package/README.md +8 -15
- package/localization/.localization.cache.json +1244 -140
- package/localization/aa.json +9 -1
- package/localization/af.json +9 -1
- package/localization/agq.json +9 -1
- package/localization/ak.json +9 -1
- package/localization/am.json +9 -1
- package/localization/ar.json +9 -1
- package/localization/as.json +9 -1
- package/localization/asa.json +9 -1
- package/localization/ast.json +9 -1
- package/localization/az.json +9 -1
- package/localization/ba.json +9 -1
- package/localization/bas.json +9 -1
- package/localization/be.json +9 -1
- package/localization/bem.json +9 -1
- package/localization/bez.json +9 -1
- package/localization/bg.json +9 -1
- package/localization/bm.json +9 -1
- package/localization/bn.json +9 -1
- package/localization/bo.json +9 -1
- package/localization/br.json +9 -1
- package/localization/brx.json +9 -1
- package/localization/bs.json +9 -1
- package/localization/byn.json +9 -1
- package/localization/ca.json +9 -1
- package/localization/ccp.json +9 -1
- package/localization/cd-RU.json +9 -1
- package/localization/ceb.json +9 -1
- package/localization/cgg.json +9 -1
- package/localization/chr.json +9 -1
- package/localization/co.json +9 -1
- package/localization/cs.json +9 -1
- package/localization/cu-RU.json +9 -1
- package/localization/da.json +9 -1
- package/localization/de-AT.json +9 -1
- package/localization/de-CH.json +9 -1
- package/localization/de-DE.json +9 -1
- package/localization/dua.json +9 -1
- package/localization/dv.json +9 -1
- package/localization/dz.json +9 -1
- package/localization/ebu.json +9 -1
- package/localization/en.json +9 -1
- package/localization/es-ES.json +9 -1
- package/localization/es-MX.json +9 -1
- package/localization/et.json +9 -1
- package/localization/eu.json +9 -1
- package/localization/fr-CA.json +9 -1
- package/localization/fr-CH.json +9 -1
- package/localization/fr-FR.json +9 -1
- package/localization/gsw.json +9 -1
- package/localization/hi.json +9 -1
- package/localization/hr.json +9 -1
- package/localization/hy.json +9 -1
- package/localization/ja.json +9 -1
- package/localization/km.json +9 -1
- package/localization/ksf.json +9 -1
- package/localization/ku.json +9 -1
- package/localization/kw.json +9 -1
- package/localization/my.json +9 -1
- package/localization/nl.json +9 -1
- package/localization/prs.json +9 -1
- package/localization/reference.js +10 -1
- package/localization/ru.json +9 -1
- package/localization/sq.json +9 -1
- package/localization/swc.json +9 -1
- package/localization/th.json +9 -1
- package/localization/tzm-Latn-.json +9 -1
- package/localization/uk.json +9 -1
- package/localization/vi.json +9 -1
- package/localization/zh-Hans.json +9 -1
- package/localization/zh-Hant.json +9 -1
- package/npm-shrinkwrap.json +6498 -676
- package/package.json +19 -1
- package/src/commands/list-models.js +1 -0
- package/src/commands/translate.js +42 -9
- package/src/lib/config.js +1 -1
- package/src/lib/context-keys.js +12 -3
- package/src/lib/io.js +10 -3
- package/src/lib/reference-loader.js +1 -0
- package/src/main.mjs +2 -2
- package/test/README.md +37 -0
- package/test/common.mjs +29 -0
- package/test/config.test.js +78 -0
- package/test/fixtures/reference.js +5 -0
- package/test/fixtures/reference.json +5 -0
- package/test/fixtures/reference.jsonc +8 -0
- package/test/list-models.test.js +27 -0
- package/test/localization.test.js +124 -0
- package/test/main-cli.test.js +79 -0
- package/test/mocha.setup.js +10 -0
- package/test/translate-command.test.js +122 -0
- package/test/reference.js +0 -28
- package/test/reference.json +0 -28
- package/test/reference.jsonc +0 -29
package/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@drone1/alt",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "1.0.0",
|
|
5
5
|
"description": "An AI-powered localization tool",
|
|
6
6
|
"main": "src/index.mjs",
|
|
7
7
|
"bin": {
|
|
8
8
|
"alt": "./alt.mjs"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
+
"test": "ALT_TEST=1 mocha",
|
|
12
|
+
"test:targeted": "ALT_TEST=1 mocha --grep 'multiple target'",
|
|
13
|
+
"test": "ALT_TEST=1 mocha",
|
|
14
|
+
"test:coverage": "ALT_TEST=1 nyc mocha",
|
|
11
15
|
"localize-display-strings": "./alt.mjs translate --reference-file localization/reference.js",
|
|
12
16
|
"print-all-help": "rm -f help.txt && (./alt.mjs help && echo -e '\n---\n' && ./alt.mjs help translate && echo -e '\n---\n' && ./alt.mjs help list-models) > help.txt",
|
|
13
17
|
"generate-toc": "./scripts/gh-md-toc --insert README.md && rm -f README.md.orig.* README.md.toc.* && echo '\n**README.md updated with new table of contents**'"
|
|
@@ -46,5 +50,19 @@
|
|
|
46
50
|
},
|
|
47
51
|
"engines": {
|
|
48
52
|
"node": ">=14.0.0"
|
|
53
|
+
},
|
|
54
|
+
"nyc": {
|
|
55
|
+
"check-coverage": true,
|
|
56
|
+
"per-file": true,
|
|
57
|
+
"lines": 80,
|
|
58
|
+
"statements": 80,
|
|
59
|
+
"functions": 80,
|
|
60
|
+
"branches": 80
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"chai": "^5.2.0",
|
|
64
|
+
"execa": "^9.5.2",
|
|
65
|
+
"mocha": "^11.1.0",
|
|
66
|
+
"nyc": "^17.1.0"
|
|
49
67
|
}
|
|
50
68
|
}
|
|
@@ -2,5 +2,6 @@ import { loadTranslationProvider } from '../lib/provider.js'
|
|
|
2
2
|
|
|
3
3
|
export async function runListModels({ appState, options, log }) {
|
|
4
4
|
const { apiKey, api } = await loadTranslationProvider({ __dirname: appState.__dirname, providerName: options.provider, log })
|
|
5
|
+
log.I(`Available models:\n`)
|
|
5
6
|
return log.I(await api.listModels(apiKey))
|
|
6
7
|
}
|
|
@@ -35,21 +35,41 @@ export async function runTranslation({ appState, options, log }) {
|
|
|
35
35
|
// Validate provider
|
|
36
36
|
const providerName = (options.provider ?? config.provider)?.toLowerCase()
|
|
37
37
|
if (!VALID_TRANSLATION_PROVIDERS.includes(providerName)) {
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
throw new Error(
|
|
39
|
+
(providerName
|
|
40
|
+
? localizeFormatted({
|
|
41
|
+
token: 'error-unknown-provider',
|
|
42
|
+
data: { providerName },
|
|
43
|
+
lang: appState.lang,
|
|
44
|
+
log
|
|
45
|
+
})
|
|
46
|
+
: localize({
|
|
47
|
+
token: 'error-no-provider-specified',
|
|
48
|
+
lang: appState.lang,
|
|
49
|
+
log
|
|
50
|
+
}))
|
|
51
|
+
+ localizeFormatted({
|
|
52
|
+
token: 'supported-providers',
|
|
53
|
+
data: { providers: VALID_TRANSLATION_PROVIDERS.join(', ') },
|
|
54
|
+
lang: appState.lang,
|
|
55
|
+
log
|
|
56
|
+
})
|
|
57
|
+
)
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
const referenceLanguage = options.referenceLanguage || config.referenceLanguage
|
|
43
61
|
if (!referenceLanguage || !referenceLanguage.length) {
|
|
44
|
-
|
|
45
|
-
|
|
62
|
+
throw new Error(
|
|
63
|
+
localize({ token: 'error-no-reference-language', lang: appState.lang, log })
|
|
64
|
+
)
|
|
46
65
|
}
|
|
47
66
|
|
|
48
67
|
// Get target languages from CLI or config
|
|
49
68
|
const targetLanguages = options.targetLanguages || config.targetLanguages
|
|
50
69
|
if (!targetLanguages || !targetLanguages.length) {
|
|
51
|
-
|
|
52
|
-
|
|
70
|
+
throw new Error(
|
|
71
|
+
localize({ token: 'error-no-target-languages', lang: appState.lang, log })
|
|
72
|
+
)
|
|
53
73
|
}
|
|
54
74
|
|
|
55
75
|
const normalizeOutputFilenames = options.normalizeOutputFilenames || config.normalizeOutputFilenames
|
|
@@ -71,8 +91,17 @@ export async function runTranslation({ appState, options, log }) {
|
|
|
71
91
|
// Copy to a temp location first so we can ensure it has an .mjs extension
|
|
72
92
|
const referenceData = await loadReferenceFile({ appLang: appState.lang, options, tmpDir, log })
|
|
73
93
|
if (!referenceData) {
|
|
74
|
-
|
|
75
|
-
|
|
94
|
+
throw new Error(
|
|
95
|
+
localizeFormatted({
|
|
96
|
+
token: 'error-no-reference-data-in-variable',
|
|
97
|
+
data: {
|
|
98
|
+
referenceExportedVarName: options.referenceExportedVarName,
|
|
99
|
+
referenceFile: options.referenceFile
|
|
100
|
+
},
|
|
101
|
+
lang: appState.lang,
|
|
102
|
+
log
|
|
103
|
+
})
|
|
104
|
+
)
|
|
76
105
|
}
|
|
77
106
|
|
|
78
107
|
const referenceHash = calculateHash(await readFileAsText(options.referenceFile))
|
|
@@ -94,6 +123,8 @@ export async function runTranslation({ appState, options, log }) {
|
|
|
94
123
|
const { apiKey, api: translationProvider } = await loadTranslationProvider({ __dirname: appState.__dirname, providerName, log })
|
|
95
124
|
log.V(`translation provider "${providerName}" loaded`)
|
|
96
125
|
|
|
126
|
+
log.D(`options.lookForContextData=${options.lookForContextData}`)
|
|
127
|
+
log.D(`config.lookForContextData=${config.lookForContextData}`)
|
|
97
128
|
const addContextToTranslation = options.lookForContextData || config.lookForContextData
|
|
98
129
|
|
|
99
130
|
const workQueue = []
|
|
@@ -132,9 +163,11 @@ export async function runTranslation({ appState, options, log }) {
|
|
|
132
163
|
if (addContextToTranslation) {
|
|
133
164
|
keysToProcess = keysToProcess
|
|
134
165
|
.filter(key => !isContextKey({
|
|
166
|
+
appLang: appState.lang,
|
|
135
167
|
key,
|
|
136
168
|
contextPrefix,
|
|
137
|
-
contextSuffix
|
|
169
|
+
contextSuffix,
|
|
170
|
+
log
|
|
138
171
|
}))
|
|
139
172
|
}
|
|
140
173
|
|
package/src/lib/config.js
CHANGED
|
@@ -16,7 +16,7 @@ export async function loadConfig({ configFile, refFileDir, log }) {
|
|
|
16
16
|
return await readJsonFile(configFilePath) || {
|
|
17
17
|
provider: null,
|
|
18
18
|
targetLanguages: [],
|
|
19
|
-
lookForContextData:
|
|
19
|
+
lookForContextData: false,
|
|
20
20
|
contextPrefix: '',
|
|
21
21
|
contextSuffix: '',
|
|
22
22
|
referenceLanguage: null,
|
package/src/lib/context-keys.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
import { localizeFormatted } from '../localizer/localize.js'
|
|
2
|
+
|
|
3
|
+
export function isContextKey({ appLang, key, contextPrefix, contextSuffix, log }) {
|
|
2
4
|
if (contextPrefix?.length) return key.startsWith(contextPrefix)
|
|
3
5
|
if (contextSuffix?.length) return key.endsWith(contextSuffix)
|
|
4
|
-
|
|
6
|
+
log.D(`contextPrefix=${contextPrefix}`)
|
|
7
|
+
log.D(`contextSuffix=${contextSuffix}`)
|
|
8
|
+
throw new Error(
|
|
9
|
+
localizeFormatted({
|
|
10
|
+
token: 'error-context-prefix-and-suffix-not-defined',
|
|
11
|
+
lang: appLang,
|
|
12
|
+
log
|
|
13
|
+
})
|
|
14
|
+
)
|
|
5
15
|
}
|
|
6
16
|
|
|
7
17
|
export function formatContextKeyFromKey({ key, prefix, suffix }) {
|
|
8
18
|
return `${prefix}${key}${suffix}`
|
|
9
19
|
}
|
|
10
|
-
|
package/src/lib/io.js
CHANGED
|
@@ -7,6 +7,7 @@ import { ensureExtension, normalizeKey } from './utils.js'
|
|
|
7
7
|
import { assertIsObj, assertValidPath } from './assert.js'
|
|
8
8
|
import { pathToFileURL } from 'url'
|
|
9
9
|
import { CWD } from './consts.js'
|
|
10
|
+
import { localizeFormatted } from '../localizer/localize.js'
|
|
10
11
|
|
|
11
12
|
export async function mkTmpDir() {
|
|
12
13
|
return await fsp.mkdtemp(path.join(os.tmpdir(), 'alt-'))
|
|
@@ -57,15 +58,21 @@ export function rmDir(dir, log) {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
// This is basically so that we can dynamically import .js files by copying them to temp .mjs files, to avoid errors from node
|
|
60
|
-
export async function copyFileToTempAndEnsureExtension({ filePath, tmpDir, ext }) {
|
|
61
|
+
export async function copyFileToTempAndEnsureExtension({ appLang, filePath, tmpDir, ext, log }) {
|
|
61
62
|
try {
|
|
62
63
|
const fileName = ensureExtension(path.basename(filePath), ext)
|
|
63
64
|
const destPath = path.join(tmpDir, fileName)
|
|
64
65
|
await fsp.copyFile(filePath, destPath)
|
|
65
66
|
return destPath
|
|
66
67
|
} catch (error) {
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
throw new Error(
|
|
69
|
+
localizeFormatted({
|
|
70
|
+
token: 'error-copying-file-to-temp-dir',
|
|
71
|
+
data: { error: error.message },
|
|
72
|
+
lang: appLang,
|
|
73
|
+
log
|
|
74
|
+
})
|
|
75
|
+
)
|
|
69
76
|
}
|
|
70
77
|
}
|
|
71
78
|
|
|
@@ -24,6 +24,7 @@ export async function loadReferenceFile({ appLang, options: { referenceFile, ref
|
|
|
24
24
|
|
|
25
25
|
// For .js, we need to copy to a temp location as an .mjs so we can dynamically import
|
|
26
26
|
const tmpReferencePath = await copyFileToTempAndEnsureExtension({
|
|
27
|
+
appLang,
|
|
27
28
|
filePath: referenceFile,
|
|
28
29
|
tmpDir,
|
|
29
30
|
ext: 'mjs',
|
package/src/main.mjs
CHANGED
|
@@ -81,7 +81,7 @@ export async function run() {
|
|
|
81
81
|
const command = this.name()
|
|
82
82
|
const options = this.opts()
|
|
83
83
|
|
|
84
|
-
if (options.logo) {
|
|
84
|
+
if (options.logo && process.env.ALT_TEST !== '1') {
|
|
85
85
|
await printLogo({
|
|
86
86
|
fontsSrcDir: path.resolve(__dirname, '../assets/figlet-fonts/'),
|
|
87
87
|
tagline: p.description,
|
|
@@ -111,7 +111,7 @@ export async function run() {
|
|
|
111
111
|
.option('-c, --config-file <path>', `Path to config file; defaults to <output dir>/${DEFAULT_CONFIG_FILENAME}`)
|
|
112
112
|
.option('-rl, --reference-language <language>', `The reference file's language; overrides any 'referenceLanguage' config setting`)
|
|
113
113
|
.option('-o, --output-dir <path>', 'Output directory for localized files')
|
|
114
|
-
.option('-
|
|
114
|
+
.option('-tl, --target-languages <list>', `Comma-separated list of language codes; overrides any 'targetLanguages' config setting`, value => languageList(value, log))
|
|
115
115
|
.option('-k, --keys <list>', 'Comma-separated list of keys to process', keyList)
|
|
116
116
|
.option('-R, --reference-exported-var-name <var name>', `For .js or .mjs reference files, this will be the exported variable, e.g. for 'export default = {...}' you'd use 'default' here, or 'data' for 'export const data = { ... }'. For .json or .jsonc reference files, this value is ignored.`, 'default')
|
|
117
117
|
.option('-m, --app-context-message <message>', `Description of your app to give context. Passed with each translation request; overrides any 'appContextMessage' config setting`)
|
package/test/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# ALT Testing Guide
|
|
2
|
+
|
|
3
|
+
This directory contains tests for the ALT localization tool, using Mocha, Chai, and Execa.
|
|
4
|
+
|
|
5
|
+
## Test Structure
|
|
6
|
+
|
|
7
|
+
- `mock.test.js`: Simple tests that run without external dependencies
|
|
8
|
+
- `cli-translation.test.js`: Tests for the core translation CLI functionality
|
|
9
|
+
- `config.test.js`: Tests for configuration handling
|
|
10
|
+
- `list-models.test.js`: Tests for the list-models command
|
|
11
|
+
- `localization.test.js`: Tests for the localization system
|
|
12
|
+
- `main-cli.test.js`: Tests for the main CLI interface
|
|
13
|
+
- `translate-command.test.js`: Tests for the translate command
|
|
14
|
+
|
|
15
|
+
## Setup
|
|
16
|
+
ANTHROPIC_API_KEY, GOOGLE_API_KEY, OPENAI_API_KEY must be set in order to run some tests.
|
|
17
|
+
|
|
18
|
+
## Running Tests
|
|
19
|
+
|
|
20
|
+
Run all tests (requires API keys):
|
|
21
|
+
```
|
|
22
|
+
npm test
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Run specific test:
|
|
26
|
+
```
|
|
27
|
+
npx mocha --grep "test description"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Run tests with coverage:
|
|
31
|
+
```
|
|
32
|
+
npm run test:coverage
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Test Data
|
|
36
|
+
|
|
37
|
+
The `fixtures` directory contains test fixtures used by the tests.
|
package/test/common.mjs
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
import { DEFAULT_CACHE_FILENAME } from '../src/lib/consts.js'
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
7
|
+
const __dirname = path.dirname(__filename)
|
|
8
|
+
export const SRC_DATA_DIR = path.join(__dirname, 'fixtures')
|
|
9
|
+
|
|
10
|
+
export function cleanupCacheFile(dir) {
|
|
11
|
+
cleanupFile(path.resolve(dir, DEFAULT_CACHE_FILENAME))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ensureDir(dir) {
|
|
15
|
+
if (fs.existsSync(dir)) return
|
|
16
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function cleanupFile(file) {
|
|
20
|
+
if (!fs.existsSync(file)) return
|
|
21
|
+
fs.unlinkSync(file)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function cleanupDir(dir) {
|
|
25
|
+
if (!fs.existsSync(dir)) return
|
|
26
|
+
fs.rmdirSync(dir, { recursive: true })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { expect } from 'chai'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
import { dirname } from 'path'
|
|
6
|
+
import { execa } from 'execa'
|
|
7
|
+
import { cleanupCacheFile, cleanupDir, cleanupFile, ensureDir, SRC_DATA_DIR } from './common.mjs'
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
10
|
+
const __dirname = dirname(__filename)
|
|
11
|
+
const TEST_CONFIG_DIR = path.join(__dirname, 'test-config')
|
|
12
|
+
|
|
13
|
+
describe('config functionality', () => {
|
|
14
|
+
before(() => {
|
|
15
|
+
// Create test directory if it doesn't exist
|
|
16
|
+
ensureDir(TEST_CONFIG_DIR)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
// Clean up test config file after each test
|
|
21
|
+
const configPath = path.join(TEST_CONFIG_DIR, 'config.json')
|
|
22
|
+
cleanupFile(configPath)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
after(() => {
|
|
26
|
+
cleanupDir(TEST_CONFIG_DIR)
|
|
27
|
+
cleanupCacheFile(SRC_DATA_DIR)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('should use custom config file when specified', async function() {
|
|
31
|
+
this.timeout(5000)
|
|
32
|
+
|
|
33
|
+
// Create a custom config file
|
|
34
|
+
const configPath = path.join(TEST_CONFIG_DIR, 'config.json')
|
|
35
|
+
const configContent = {
|
|
36
|
+
provider: 'anthropic',
|
|
37
|
+
targetLanguages: [
|
|
38
|
+
'fr-FR',
|
|
39
|
+
'es-ES'
|
|
40
|
+
],
|
|
41
|
+
lookForContextData: true,
|
|
42
|
+
contextPrefix: 'TEST_PREFIX',
|
|
43
|
+
contextSuffix: 'TEST_SUFFIX',
|
|
44
|
+
referenceLanguage: 'en',
|
|
45
|
+
normalizeOutputFilenames: true
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fs.writeFileSync(configPath, JSON.stringify(configContent, null, 2))
|
|
49
|
+
|
|
50
|
+
// Create a simple reference file for testing
|
|
51
|
+
const refPath = path.join(TEST_CONFIG_DIR, 'reference.js')
|
|
52
|
+
fs.writeFileSync(refPath, 'export default { "test-key": "This is a test" }')
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
// Run the CLI with the custom config
|
|
56
|
+
const result = await execa('node', [
|
|
57
|
+
path.resolve(__dirname, '../alt.mjs'),
|
|
58
|
+
'translate',
|
|
59
|
+
'-r',
|
|
60
|
+
refPath,
|
|
61
|
+
'--config-file',
|
|
62
|
+
configPath,
|
|
63
|
+
'--debug'
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
// Command should succeed
|
|
67
|
+
expect(result.exitCode).to.equal(0)
|
|
68
|
+
|
|
69
|
+
// Output should include info from our config
|
|
70
|
+
expect(result.stdout).to.include('anthropic')
|
|
71
|
+
expect(result.stdout).to.include('fr-FR')
|
|
72
|
+
expect(result.stdout).to.include('es-ES')
|
|
73
|
+
} finally {
|
|
74
|
+
// Clean up reference file
|
|
75
|
+
cleanupFile(refPath)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { execa } from 'execa'
|
|
2
|
+
import { expect } from 'chai'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import { fileURLToPath } from 'url'
|
|
6
|
+
import { dirname } from 'path'
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
9
|
+
const __dirname = dirname(__filename)
|
|
10
|
+
|
|
11
|
+
describe('list-models command', () => {
|
|
12
|
+
it('should display models from a specific provider when specified', async () => {
|
|
13
|
+
// Run with specific provider (using anthropic as example)
|
|
14
|
+
const result = await execa('node', [
|
|
15
|
+
path.resolve(__dirname, '../alt.mjs'),
|
|
16
|
+
'list-models',
|
|
17
|
+
'-p',
|
|
18
|
+
'anthropic'
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
// Check the command executed successfully
|
|
22
|
+
expect(result.exitCode).to.equal(0)
|
|
23
|
+
|
|
24
|
+
// Output should include provider-specific information
|
|
25
|
+
expect(result.stdout).to.include('Available models')
|
|
26
|
+
})
|
|
27
|
+
})
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { expect } from 'chai'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
import { dirname } from 'path'
|
|
6
|
+
import { isBcp47LanguageTagValid, isDefaultLanguage, initLocalizer } from '../src/localizer/localize.js'
|
|
7
|
+
import { LANGTAG_DEFAULT } from '../src/lib/consts.js'
|
|
8
|
+
import { ensureDir, cleanupDir } from './common.mjs'
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
11
|
+
const __dirname = dirname(__filename)
|
|
12
|
+
const TEST_LOCALIZATION_DIR = path.join(__dirname, 'test-localization')
|
|
13
|
+
|
|
14
|
+
describe('localization functionality', () => {
|
|
15
|
+
before(() => {
|
|
16
|
+
// Create test directory if it doesn't exist
|
|
17
|
+
ensureDir(TEST_LOCALIZATION_DIR)
|
|
18
|
+
|
|
19
|
+
// Create a simple English localization file
|
|
20
|
+
const enContent = {
|
|
21
|
+
"test_key": "This is a test",
|
|
22
|
+
"hello_world": "Hello, World!",
|
|
23
|
+
"formatted_string": "Hello, %%name%%!"
|
|
24
|
+
}
|
|
25
|
+
fs.writeFileSync(path.join(TEST_LOCALIZATION_DIR, 'en.json'), JSON.stringify(enContent, null, 2))
|
|
26
|
+
|
|
27
|
+
// Create a simple French localization file
|
|
28
|
+
const frContent = {
|
|
29
|
+
"test_key": "C'est un test",
|
|
30
|
+
"hello_world": "Bonjour, Monde!",
|
|
31
|
+
"formatted_string": "Bonjour, %%name%%!"
|
|
32
|
+
}
|
|
33
|
+
fs.writeFileSync(path.join(TEST_LOCALIZATION_DIR, 'fr-FR.json'), JSON.stringify(frContent, null, 2))
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
after(() => {
|
|
37
|
+
cleanupDir(TEST_LOCALIZATION_DIR)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('isBcp47LanguageTagValid', () => {
|
|
41
|
+
it('should validate valid BCP47 language tags', () => {
|
|
42
|
+
expect(isBcp47LanguageTagValid('en')).to.be.an('object')
|
|
43
|
+
expect(isBcp47LanguageTagValid('fr-FR')).to.be.an('object')
|
|
44
|
+
expect(isBcp47LanguageTagValid('de-DE')).to.be.an('object')
|
|
45
|
+
expect(isBcp47LanguageTagValid('zh-Hans')).to.be.an('object')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should reject invalid BCP47 language tags', () => {
|
|
49
|
+
expect(isBcp47LanguageTagValid('not-a-language')).to.be.undefined
|
|
50
|
+
expect(isBcp47LanguageTagValid('xx-XX')).to.be.undefined
|
|
51
|
+
expect(isBcp47LanguageTagValid('')).to.be.undefined
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('isDefaultLanguage', () => {
|
|
56
|
+
it('should identify the default language', () => {
|
|
57
|
+
expect(isDefaultLanguage(LANGTAG_DEFAULT)).to.be.true
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('should reject non-default languages', () => {
|
|
61
|
+
expect(isDefaultLanguage('fr-FR')).to.be.false
|
|
62
|
+
expect(isDefaultLanguage('es-ES')).to.be.false
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('initLocalizer', () => {
|
|
67
|
+
it('should initialize with the default language', async () => {
|
|
68
|
+
const mockLog = {
|
|
69
|
+
D: () => {},
|
|
70
|
+
W: () => {},
|
|
71
|
+
E: () => {},
|
|
72
|
+
I: () => {},
|
|
73
|
+
V: () => {}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const lang = await initLocalizer({
|
|
77
|
+
defaultAppLanguage: 'en',
|
|
78
|
+
appLanguage: null,
|
|
79
|
+
srcDir: TEST_LOCALIZATION_DIR,
|
|
80
|
+
log: mockLog
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
expect(lang).to.equal('en')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should initialize with a specified language', async () => {
|
|
87
|
+
const mockLog = {
|
|
88
|
+
D: () => {},
|
|
89
|
+
W: () => {},
|
|
90
|
+
E: () => {},
|
|
91
|
+
I: () => {},
|
|
92
|
+
V: () => {}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const lang = await initLocalizer({
|
|
96
|
+
defaultAppLanguage: 'en',
|
|
97
|
+
appLanguage: 'fr-FR',
|
|
98
|
+
srcDir: TEST_LOCALIZATION_DIR,
|
|
99
|
+
log: mockLog
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
expect(lang).to.equal('fr-FR')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('should fall back to default language when invalid language specified', async () => {
|
|
106
|
+
const mockLog = {
|
|
107
|
+
D: () => {},
|
|
108
|
+
W: () => {},
|
|
109
|
+
E: () => {},
|
|
110
|
+
I: () => {},
|
|
111
|
+
V: () => {}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const lang = await initLocalizer({
|
|
115
|
+
defaultAppLanguage: 'en',
|
|
116
|
+
appLanguage: 'invalid-language',
|
|
117
|
+
srcDir: TEST_LOCALIZATION_DIR,
|
|
118
|
+
log: mockLog
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
expect(lang).to.equal('en')
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
})
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { execa } from 'execa'
|
|
2
|
+
import { expect } from 'chai'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
import { dirname } from 'path'
|
|
5
|
+
import { cleanupCacheFile, SRC_DATA_DIR } from './common.mjs'
|
|
6
|
+
import path from 'path'
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
9
|
+
const __dirname = dirname(__filename)
|
|
10
|
+
|
|
11
|
+
describe('main CLI functionality', () => {
|
|
12
|
+
after(() => cleanupCacheFile(SRC_DATA_DIR))
|
|
13
|
+
|
|
14
|
+
it('should display help information', async () => {
|
|
15
|
+
// Run the help command
|
|
16
|
+
const result = await execa('node', [
|
|
17
|
+
path.resolve(__dirname, '../alt.mjs'),
|
|
18
|
+
'--help'
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
// Check the command executed successfully
|
|
22
|
+
expect(result.exitCode).to.equal(0)
|
|
23
|
+
|
|
24
|
+
// Output should contain expected help information
|
|
25
|
+
expect(result.stdout).to.include('Usage:')
|
|
26
|
+
expect(result.stdout).to.include('Options:')
|
|
27
|
+
expect(result.stdout).to.include('Commands:')
|
|
28
|
+
expect(result.stdout).to.include('Environment variables:')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should display version information', async () => {
|
|
32
|
+
// Run the version command
|
|
33
|
+
const result = await execa('node', [
|
|
34
|
+
path.resolve(__dirname, '../alt.mjs'),
|
|
35
|
+
'--version'
|
|
36
|
+
])
|
|
37
|
+
|
|
38
|
+
// Check the command executed successfully
|
|
39
|
+
expect(result.exitCode).to.equal(0)
|
|
40
|
+
|
|
41
|
+
// Output should contain version number
|
|
42
|
+
expect(result.stdout).to.match(/\d+\.\d+\.\d+/)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('should display command-specific help', async () => {
|
|
46
|
+
// Run the help command for translate
|
|
47
|
+
const result = await execa('node', [
|
|
48
|
+
path.resolve(__dirname, '../alt.mjs'),
|
|
49
|
+
'help',
|
|
50
|
+
'translate'
|
|
51
|
+
])
|
|
52
|
+
|
|
53
|
+
// Check the command executed successfully
|
|
54
|
+
expect(result.exitCode).to.equal(0)
|
|
55
|
+
|
|
56
|
+
// Output should contain expected help information for translate command
|
|
57
|
+
expect(result.stdout).to.include('Usage: alt translate')
|
|
58
|
+
expect(result.stdout).to.include('-r, --reference-file')
|
|
59
|
+
expect(result.stdout).to.include('-tl, --target-languages')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should return error for missing required options', async () => {
|
|
63
|
+
try {
|
|
64
|
+
// Run translate command without required options
|
|
65
|
+
await execa('node', [
|
|
66
|
+
path.resolve(__dirname, '../alt.mjs'),
|
|
67
|
+
'translate'
|
|
68
|
+
])
|
|
69
|
+
// Should not reach here as the command should fail
|
|
70
|
+
expect.fail('Command should have failed with missing required options')
|
|
71
|
+
} catch (error) {
|
|
72
|
+
// Check that the command failed
|
|
73
|
+
expect(error.exitCode).to.not.equal(0)
|
|
74
|
+
|
|
75
|
+
// Error should mention missing required option
|
|
76
|
+
expect(error.stderr).to.include('required option')
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Mocha setup file
|
|
2
|
+
// This file sets up the environment for testing
|
|
3
|
+
|
|
4
|
+
// Set timeout for all tests to 10 seconds by default
|
|
5
|
+
export const mochaHooks = {
|
|
6
|
+
beforeAll() {
|
|
7
|
+
// Default timeout for tests (can be overridden in individual tests)
|
|
8
|
+
this.timeout(10000);
|
|
9
|
+
}
|
|
10
|
+
};
|