@drone1/alt 0.7.2 → 0.8.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/README.md +99 -25
- package/package.json +3 -2
- package/scripts/gh-md-toc +421 -0
- package/src/commands/translate.js +4 -4
- package/src/lib/consts.js +3 -0
- package/src/main.mjs +3 -3
- package/src/providers/google.mjs +80 -0
- package/test/reference.js +28 -0
- package/test/reference.json +28 -0
- package/test/reference.jsonc +29 -0
package/README.md
CHANGED
|
@@ -13,15 +13,46 @@
|
|
|
13
13
|

|
|
14
14
|
[](https://opensource.org/licenses/MIT)
|
|
15
15
|
|
|
16
|
+
<!--ts-->
|
|
17
|
+
* [AI Localization Tool](#ai-localization-tool)
|
|
18
|
+
* [Features](#features)
|
|
19
|
+
* [Installation](#installation)
|
|
20
|
+
* [Setup](#setup)
|
|
21
|
+
* [Create a reference file](#create-a-reference-file)
|
|
22
|
+
* [Running](#running)
|
|
23
|
+
* [Output](#output)
|
|
24
|
+
* [Config file](#config-file)
|
|
25
|
+
* [Adding context](#adding-context)
|
|
26
|
+
* [Application-level context](#application-level-context)
|
|
27
|
+
* [String-specific context](#string-specific-context)
|
|
28
|
+
* [Display language](#display-language)
|
|
29
|
+
* [Usage](#usage)
|
|
30
|
+
* [Examples](#examples)
|
|
31
|
+
* [Example I](#example-i)
|
|
32
|
+
* [Example II](#example-ii)
|
|
33
|
+
* [Example III](#example-iii)
|
|
34
|
+
* [Example: ALT's localized display strings](#example-alts-localized-display-strings)
|
|
35
|
+
* [Formatting](#formatting)
|
|
36
|
+
* [Translation rules](#translation-rules)
|
|
37
|
+
* [Additional notes](#additional-notes)
|
|
38
|
+
* [Delayed vs. realtime writes](#delayed-vs-realtime-writes)
|
|
39
|
+
* [CI](#ci)
|
|
40
|
+
* [PR](#pr)
|
|
41
|
+
|
|
42
|
+
<!-- Created by https://github.com/ekalinin/github-markdown-toc -->
|
|
43
|
+
<!-- Added by: jonl, at: Wed Apr 16 11:56:33 AM CEST 2025 -->
|
|
44
|
+
|
|
45
|
+
<!--te-->
|
|
46
|
+
|
|
16
47
|
# AI Localization Tool
|
|
17
|
-
Translates all source strings in a reference (
|
|
48
|
+
Translates all source strings in a reference (`js`,`mjs`,`json`,`jsonc`) file to all target languages using AI.
|
|
18
49
|
|
|
19
50
|
## Features
|
|
20
51
|
* Loads source/reference key/value pairs from a file
|
|
21
52
|
* Localizes using AI as needed, writing to a .json file per language
|
|
22
53
|
* App-level context can be specified [`appContextMessage`]
|
|
23
54
|
* Additional context can be specified per string [`--contextPrefix`, `--contextSuffix`]
|
|
24
|
-
* Supports
|
|
55
|
+
* Supports Claude, Gemini, OpenAI [`--provider`]
|
|
25
56
|
* User-modifications to output files are safe and will not be overwritten
|
|
26
57
|
* Languages are specified using BCP47 tags
|
|
27
58
|
|
|
@@ -30,7 +61,10 @@ Translates all source strings in a reference (`.js`,`.mjs`,`.json`,`.jsonc`) fil
|
|
|
30
61
|
npm install -g @drone1/alt
|
|
31
62
|
```
|
|
32
63
|
## Setup
|
|
33
|
-
Create a reference file
|
|
64
|
+
### Create a reference file
|
|
65
|
+
This will house your source strings via key/value pairs in your preferred language.
|
|
66
|
+
|
|
67
|
+
Here's an example ``reference.js``:
|
|
34
68
|
```javascript
|
|
35
69
|
export default {
|
|
36
70
|
'error-msg': `Sorry, we don't know how to do anything`,
|
|
@@ -38,22 +72,27 @@ export default {
|
|
|
38
72
|
'_context:success-msg': `This text is for a button when a user completes a task`
|
|
39
73
|
}
|
|
40
74
|
```
|
|
41
|
-
Use whatever filename you prefer
|
|
42
|
-
You can specify an exported variable instead of using `default`. See `--referenceVarName`.
|
|
75
|
+
Use whatever filename you prefer. `js`,`mjs`,`json`,`jsonc` extensions are supported.
|
|
43
76
|
|
|
44
|
-
|
|
77
|
+
For `.js` and `.mjs` files, you can specify the name of an exported variable instead of using `default`, via `--referenceVarName`.
|
|
78
|
+
|
|
79
|
+
### Running
|
|
45
80
|
```bash
|
|
46
81
|
ANTHROPIC_API_KEY=<secret>
|
|
47
|
-
alt translate --reference-file ./reference.js --reference-language en --target-languages aa,bo,es-MX,hi,zh-
|
|
48
|
-
```
|
|
49
|
-
or
|
|
50
|
-
```bash
|
|
51
|
-
OPENAI_API_KEY=<secret>
|
|
52
|
-
alt translate --reference-file ./reference.js --reference-language en --target-languages aa,bo,es-MX,hi,zh-SG --provider openai
|
|
82
|
+
alt translate --reference-file ./reference.js --reference-language en --target-languages aa,bo,es-MX,hi,zh-Hans --provider anthropic
|
|
53
83
|
```
|
|
54
|
-
|
|
84
|
+
This command would iterate across all key/value pairs defined in `./reference.js` and translate if needed.
|
|
85
|
+
|
|
86
|
+
Here are all supported providers and their required environment variable
|
|
87
|
+
|
|
88
|
+
| `-p`, `--provider` | <span style="font-weight: normal;">environment variable</span> |
|
|
89
|
+
|-------------------|----------------------------------------------------------------|
|
|
90
|
+
| anthropic | ANTHROPIC_API_KEY |
|
|
91
|
+
| google | GOOGLE_API_KEY |
|
|
92
|
+
| openai | OPENAI_API_KEY |
|
|
55
93
|
|
|
56
|
-
|
|
94
|
+
### Output
|
|
95
|
+
The example above would write `aa.json`, `bo.json`, etc., to the current working directory.
|
|
57
96
|
|
|
58
97
|
Sample output:
|
|
59
98
|
```json
|
|
@@ -65,10 +104,6 @@ Sample output:
|
|
|
65
104
|
|
|
66
105
|
Note that output files can be lower-cased if you pass the ``--normalize-output-filenames`` option, so `fr-FR` translations would write to `fr-fr.json`
|
|
67
106
|
|
|
68
|
-
## Display language
|
|
69
|
-
ALT CLI itself has been localized so you can use it many languages. For non-English languages, you can set the display language with the `ALT_LANGUAGE` environment variable. Please feel free to submit
|
|
70
|
-
an issue if you do not see your preferred language.
|
|
71
|
-
|
|
72
107
|
## Config file
|
|
73
108
|
[_optional_] You can create a config file. By default, `ALT` will search the output directory for `config.json`, but you can specify a path directly using
|
|
74
109
|
`--config-file`.
|
|
@@ -79,7 +114,7 @@ config:
|
|
|
79
114
|
{
|
|
80
115
|
"appContextMessage": "This is a description of my app",
|
|
81
116
|
"referenceLanguage": "ar",
|
|
82
|
-
"provider": "
|
|
117
|
+
"provider": "google",
|
|
83
118
|
"lookForContextData": true,
|
|
84
119
|
"contextPrefix": "_context:",
|
|
85
120
|
"contextSuffix": "",
|
|
@@ -91,6 +126,40 @@ config:
|
|
|
91
126
|
|
|
92
127
|
Any of the above settings can be specified using command-line arguments (`--app-context-message`, `--reference-language`, `--provider`, `--target-languages`). Command-line arguments take precedence.
|
|
93
128
|
|
|
129
|
+
## Adding context
|
|
130
|
+
Sometimes a string isn't enough to give context to the AI, and as a result, it may give an undesirable translation. ALT allows you to specify additional context for this reason.
|
|
131
|
+
### Application-level context
|
|
132
|
+
A global, application description can be specified `--app-context-message` (or `appContextMessage` in a [config](#config)).
|
|
133
|
+
For example, your config may include something like:
|
|
134
|
+
```json
|
|
135
|
+
"appContextMessage": "Voided is a MMORPG game based on outer space."
|
|
136
|
+
```
|
|
137
|
+
### String-specific context
|
|
138
|
+
Context can be added for any reference key/value pairs by passing `--look-for-context-data` (or setting `lookForContextData: true` in a [config](#config)).
|
|
139
|
+
|
|
140
|
+
For example, given the following reference key/value pair:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
"editor-add-component": '+ Star',
|
|
144
|
+
```
|
|
145
|
+
This may not translate as desired, so ALT allows you to specify additional context in the form of another key/value pair. For example:
|
|
146
|
+
```json
|
|
147
|
+
"_context:editor-add-component": "This is text for a button the galaxy UI, where a user can create a star"
|
|
148
|
+
```
|
|
149
|
+
`_context:` can be whatever you prefer here. It's specified via `--context-prefix`, or `contextPrefix` in a [config](#config).
|
|
150
|
+
|
|
151
|
+
A suffix can be specified instead of (or in conjunction with) a prefix, with `--context-suffix`, or `contextSuffix` in a [config](#config). Example:
|
|
152
|
+
```json
|
|
153
|
+
"editor-add-component[context]": "This is text for a button the graph editor"
|
|
154
|
+
```
|
|
155
|
+
In this case, `[context]` would be specified by passing `--context-suffix '[context]'` or setting `"contextSuffix": "[context]"` in a [config](#config).
|
|
156
|
+
|
|
157
|
+
Further examples can be found [here](#examples).
|
|
158
|
+
|
|
159
|
+
## Display language
|
|
160
|
+
ALT CLI itself has been localized so you can use it many languages. You can optionally set the display language with the `ALT_LANGUAGE` environment variable. Please feel free to submit
|
|
161
|
+
an issue if you do not see your preferred language.
|
|
162
|
+
|
|
94
163
|
## Usage
|
|
95
164
|
```
|
|
96
165
|
Usage: alt [options] [command]
|
|
@@ -112,6 +181,7 @@ Environment variables:
|
|
|
112
181
|
ALT_LANGUAGE POSIX locale used for display
|
|
113
182
|
|
|
114
183
|
---
|
|
184
|
+
|
|
115
185
|
Usage: alt translate [options]
|
|
116
186
|
|
|
117
187
|
Options:
|
|
@@ -141,6 +211,7 @@ Options:
|
|
|
141
211
|
-h, --help display help for command
|
|
142
212
|
|
|
143
213
|
---
|
|
214
|
+
|
|
144
215
|
Usage: alt list-models [options]
|
|
145
216
|
|
|
146
217
|
Options:
|
|
@@ -213,11 +284,14 @@ Internally, there is currently nothing in the prompt about this. I've tested wit
|
|
|
213
284
|
|
|
214
285
|
Please submit an issue if it causes you any trouble.
|
|
215
286
|
|
|
216
|
-
##
|
|
217
|
-
Translation will occur for a given target language & key if any of the following are true:
|
|
218
|
-
* The
|
|
219
|
-
*
|
|
220
|
-
* The
|
|
287
|
+
## Translation rules
|
|
288
|
+
When does ALT translate a given source string? Translation will occur for a given target language & reference key/value if any of the following are true:
|
|
289
|
+
* The output file does not exist
|
|
290
|
+
* The output file is missing the reference key
|
|
291
|
+
* The reference value was modified
|
|
292
|
+
* A context value for the given target language/key is found and has been modified.
|
|
293
|
+
* `-f` or `--force` are specified
|
|
294
|
+
* The cache file (`.localization.cache.json`) is not present
|
|
221
295
|
|
|
222
296
|
Translation will _not_ occur if `alt` detects that the given value in the target language file has been manually modified. If you modify an output value manually and want it to be re-translated
|
|
223
297
|
later, you can just delete that key/value pair from the given file.
|
|
@@ -234,5 +308,5 @@ If you prefer to write updates to disk in real-time (anytime any output data cha
|
|
|
234
308
|
### CI
|
|
235
309
|
You may want to use `--tty` for more useful output.
|
|
236
310
|
|
|
237
|
-
##
|
|
311
|
+
## PR
|
|
238
312
|
Feel free to fix existing issues and submit a PR, or submit a new issue.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@drone1/alt",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0",
|
|
5
5
|
"description": "An AI-powered localization tool",
|
|
6
6
|
"main": "src/index.mjs",
|
|
7
7
|
"bin": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"localize-display-strings": "./alt.mjs translate --reference-file localization/reference.js",
|
|
12
|
-
"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"
|
|
12
|
+
"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
|
+
"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**'"
|
|
13
14
|
},
|
|
14
15
|
"repository": {
|
|
15
16
|
"type": "git",
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Steps:
|
|
5
|
+
#
|
|
6
|
+
# 1. Download corresponding html file for some README.md:
|
|
7
|
+
# curl -s $1
|
|
8
|
+
#
|
|
9
|
+
# 2. Discard rows where no substring 'user-content-' (github's markup):
|
|
10
|
+
# awk '/user-content-/ { ...
|
|
11
|
+
#
|
|
12
|
+
# 3.1 Get last number in each row like ' ... </span></a>sitemap.js</h1'.
|
|
13
|
+
# It's a level of the current header:
|
|
14
|
+
# substr($0, length($0), 1)
|
|
15
|
+
#
|
|
16
|
+
# 3.2 Get level from 3.1 and insert corresponding number of spaces before '*':
|
|
17
|
+
# sprintf("%*s", (level-1)*'"$nb_spaces"', "")
|
|
18
|
+
#
|
|
19
|
+
# 4. Find head's text and insert it inside "* [ ... ]":
|
|
20
|
+
# substr($0, match($0, /a>.*<\/h/)+2, RLENGTH-5)
|
|
21
|
+
#
|
|
22
|
+
# 5. Find anchor and insert it inside "(...)":
|
|
23
|
+
# substr($0, match($0, "href=\"[^\"]+?\" ")+6, RLENGTH-8)
|
|
24
|
+
#
|
|
25
|
+
|
|
26
|
+
gh_toc_version="0.10.0"
|
|
27
|
+
|
|
28
|
+
gh_user_agent="gh-md-toc v$gh_toc_version"
|
|
29
|
+
|
|
30
|
+
#
|
|
31
|
+
# Download rendered into html README.md by its url.
|
|
32
|
+
#
|
|
33
|
+
#
|
|
34
|
+
gh_toc_load() {
|
|
35
|
+
local gh_url=$1
|
|
36
|
+
|
|
37
|
+
if type curl &>/dev/null; then
|
|
38
|
+
curl --user-agent "$gh_user_agent" -s "$gh_url"
|
|
39
|
+
elif type wget &>/dev/null; then
|
|
40
|
+
wget --user-agent="$gh_user_agent" -qO- "$gh_url"
|
|
41
|
+
else
|
|
42
|
+
echo "Please, install 'curl' or 'wget' and try again."
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#
|
|
48
|
+
# Converts local md file into html by GitHub
|
|
49
|
+
#
|
|
50
|
+
# -> curl -X POST --data '{"text": "Hello world github/linguist#1 **cool**, and #1!"}' https://api.github.com/markdown
|
|
51
|
+
# <p>Hello world github/linguist#1 <strong>cool</strong>, and #1!</p>'"
|
|
52
|
+
gh_toc_md2html() {
|
|
53
|
+
local gh_file_md=$1
|
|
54
|
+
local skip_header=$2
|
|
55
|
+
|
|
56
|
+
URL=https://api.github.com/markdown/raw
|
|
57
|
+
|
|
58
|
+
if [ -n "$GH_TOC_TOKEN" ]; then
|
|
59
|
+
TOKEN=$GH_TOC_TOKEN
|
|
60
|
+
else
|
|
61
|
+
TOKEN_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt"
|
|
62
|
+
if [ -f "$TOKEN_FILE" ]; then
|
|
63
|
+
TOKEN="$(cat "$TOKEN_FILE")"
|
|
64
|
+
fi
|
|
65
|
+
fi
|
|
66
|
+
if [ -n "${TOKEN}" ]; then
|
|
67
|
+
AUTHORIZATION="Authorization: token ${TOKEN}"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
local gh_tmp_file_md=$gh_file_md
|
|
71
|
+
if [ "$skip_header" = "yes" ]; then
|
|
72
|
+
if grep -Fxq "<!--te-->" "$gh_src"; then
|
|
73
|
+
# cut everything before the toc
|
|
74
|
+
gh_tmp_file_md=$gh_file_md~~
|
|
75
|
+
sed '1,/<!--te-->/d' "$gh_file_md" > "$gh_tmp_file_md"
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# echo $URL 1>&2
|
|
80
|
+
OUTPUT=$(curl -s \
|
|
81
|
+
--user-agent "$gh_user_agent" \
|
|
82
|
+
--data-binary @"$gh_tmp_file_md" \
|
|
83
|
+
-H "Content-Type:text/plain" \
|
|
84
|
+
-H "$AUTHORIZATION" \
|
|
85
|
+
"$URL")
|
|
86
|
+
|
|
87
|
+
rm -f "${gh_file_md}~~"
|
|
88
|
+
|
|
89
|
+
if [ "$?" != "0" ]; then
|
|
90
|
+
echo "XXNetworkErrorXX"
|
|
91
|
+
fi
|
|
92
|
+
if [ "$(echo "${OUTPUT}" | awk '/API rate limit exceeded/')" != "" ]; then
|
|
93
|
+
echo "XXRateLimitXX"
|
|
94
|
+
else
|
|
95
|
+
echo "${OUTPUT}"
|
|
96
|
+
fi
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
#
|
|
101
|
+
# Is passed string url
|
|
102
|
+
#
|
|
103
|
+
gh_is_url() {
|
|
104
|
+
case $1 in
|
|
105
|
+
https* | http*)
|
|
106
|
+
echo "yes";;
|
|
107
|
+
*)
|
|
108
|
+
echo "no";;
|
|
109
|
+
esac
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#
|
|
113
|
+
# TOC generator
|
|
114
|
+
#
|
|
115
|
+
gh_toc(){
|
|
116
|
+
local gh_src=$1
|
|
117
|
+
local gh_src_copy=$1
|
|
118
|
+
local gh_ttl_docs=$2
|
|
119
|
+
local need_replace=$3
|
|
120
|
+
local no_backup=$4
|
|
121
|
+
local no_footer=$5
|
|
122
|
+
local indent=$6
|
|
123
|
+
local skip_header=$7
|
|
124
|
+
|
|
125
|
+
if [ "$gh_src" = "" ]; then
|
|
126
|
+
echo "Please, enter URL or local path for a README.md"
|
|
127
|
+
exit 1
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# Show "TOC" string only if working with one document
|
|
132
|
+
if [ "$gh_ttl_docs" = "1" ]; then
|
|
133
|
+
|
|
134
|
+
echo "Table of Contents"
|
|
135
|
+
echo "================="
|
|
136
|
+
echo ""
|
|
137
|
+
gh_src_copy=""
|
|
138
|
+
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
if [ "$(gh_is_url "$gh_src")" == "yes" ]; then
|
|
142
|
+
gh_toc_load "$gh_src" | gh_toc_grab "$gh_src_copy" "$indent"
|
|
143
|
+
if [ "${PIPESTATUS[0]}" != "0" ]; then
|
|
144
|
+
echo "Could not load remote document."
|
|
145
|
+
echo "Please check your url or network connectivity"
|
|
146
|
+
exit 1
|
|
147
|
+
fi
|
|
148
|
+
if [ "$need_replace" = "yes" ]; then
|
|
149
|
+
echo
|
|
150
|
+
echo "!! '$gh_src' is not a local file"
|
|
151
|
+
echo "!! Can't insert the TOC into it."
|
|
152
|
+
echo
|
|
153
|
+
fi
|
|
154
|
+
else
|
|
155
|
+
local rawhtml
|
|
156
|
+
rawhtml=$(gh_toc_md2html "$gh_src" "$skip_header")
|
|
157
|
+
if [ "$rawhtml" == "XXNetworkErrorXX" ]; then
|
|
158
|
+
echo "Parsing local markdown file requires access to github API"
|
|
159
|
+
echo "Please make sure curl is installed and check your network connectivity"
|
|
160
|
+
exit 1
|
|
161
|
+
fi
|
|
162
|
+
if [ "$rawhtml" == "XXRateLimitXX" ]; then
|
|
163
|
+
echo "Parsing local markdown file requires access to github API"
|
|
164
|
+
echo "Error: You exceeded the hourly limit. See: https://developer.github.com/v3/#rate-limiting"
|
|
165
|
+
TOKEN_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt"
|
|
166
|
+
echo "or place GitHub auth token here: ${TOKEN_FILE}"
|
|
167
|
+
exit 1
|
|
168
|
+
fi
|
|
169
|
+
local toc
|
|
170
|
+
toc=`echo "$rawhtml" | gh_toc_grab "$gh_src_copy" "$indent"`
|
|
171
|
+
echo "$toc"
|
|
172
|
+
if [ "$need_replace" = "yes" ]; then
|
|
173
|
+
if grep -Fxq "<!--ts-->" "$gh_src" && grep -Fxq "<!--te-->" "$gh_src"; then
|
|
174
|
+
echo "Found markers"
|
|
175
|
+
else
|
|
176
|
+
echo "You don't have <!--ts--> or <!--te--> in your file...exiting"
|
|
177
|
+
exit 1
|
|
178
|
+
fi
|
|
179
|
+
local ts="<\!--ts-->"
|
|
180
|
+
local te="<\!--te-->"
|
|
181
|
+
local dt
|
|
182
|
+
dt=$(date +'%F_%H%M%S')
|
|
183
|
+
local ext=".orig.${dt}"
|
|
184
|
+
local toc_path="${gh_src}.toc.${dt}"
|
|
185
|
+
local toc_createdby="<!-- Created by https://github.com/ekalinin/github-markdown-toc -->"
|
|
186
|
+
local toc_footer
|
|
187
|
+
toc_footer="<!-- Added by: `whoami`, at: `date` -->"
|
|
188
|
+
# http://fahdshariff.blogspot.ru/2012/12/sed-mutli-line-replacement-between-two.html
|
|
189
|
+
# clear old TOC
|
|
190
|
+
sed -i"${ext}" "/${ts}/,/${te}/{//!d;}" "$gh_src"
|
|
191
|
+
# create toc file
|
|
192
|
+
echo "${toc}" > "${toc_path}"
|
|
193
|
+
if [ "${no_footer}" != "yes" ]; then
|
|
194
|
+
echo -e "\n${toc_createdby}\n${toc_footer}\n" >> "$toc_path"
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
# insert toc file
|
|
198
|
+
if ! sed --version > /dev/null 2>&1; then
|
|
199
|
+
sed -i "" "/${ts}/r ${toc_path}" "$gh_src"
|
|
200
|
+
else
|
|
201
|
+
sed -i "/${ts}/r ${toc_path}" "$gh_src"
|
|
202
|
+
fi
|
|
203
|
+
echo
|
|
204
|
+
if [ "${no_backup}" = "yes" ]; then
|
|
205
|
+
rm "$toc_path" "$gh_src$ext"
|
|
206
|
+
fi
|
|
207
|
+
echo "!! TOC was added into: '$gh_src'"
|
|
208
|
+
if [ -z "${no_backup}" ]; then
|
|
209
|
+
echo "!! Origin version of the file: '${gh_src}${ext}'"
|
|
210
|
+
echo "!! TOC added into a separate file: '${toc_path}'"
|
|
211
|
+
fi
|
|
212
|
+
echo
|
|
213
|
+
fi
|
|
214
|
+
fi
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#
|
|
218
|
+
# Grabber of the TOC from rendered html
|
|
219
|
+
#
|
|
220
|
+
# $1 - a source url of document.
|
|
221
|
+
# It's need if TOC is generated for multiple documents.
|
|
222
|
+
# $2 - number of spaces used to indent.
|
|
223
|
+
#
|
|
224
|
+
gh_toc_grab() {
|
|
225
|
+
|
|
226
|
+
href_regex="/href=\"[^\"]+?\"/"
|
|
227
|
+
common_awk_script='
|
|
228
|
+
modified_href = ""
|
|
229
|
+
split(href, chars, "")
|
|
230
|
+
for (i=1;i <= length(href); i++) {
|
|
231
|
+
c = chars[i]
|
|
232
|
+
res = ""
|
|
233
|
+
if (c == "+") {
|
|
234
|
+
res = " "
|
|
235
|
+
} else {
|
|
236
|
+
if (c == "%") {
|
|
237
|
+
res = "\\x"
|
|
238
|
+
} else {
|
|
239
|
+
res = c ""
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
modified_href = modified_href res
|
|
243
|
+
}
|
|
244
|
+
print sprintf("%*s", (level-1)*'"$2"', "") "* [" text "](" gh_url modified_href ")"
|
|
245
|
+
'
|
|
246
|
+
if [ "`uname -s`" == "OS/390" ]; then
|
|
247
|
+
grepcmd="pcregrep -o"
|
|
248
|
+
echoargs=""
|
|
249
|
+
awkscript='{
|
|
250
|
+
level = substr($0, 3, 1)
|
|
251
|
+
text = substr($0, match($0, /<\/span><\/a>[^<]*<\/h/)+11, RLENGTH-14)
|
|
252
|
+
href = substr($0, match($0, '$href_regex')+6, RLENGTH-7)
|
|
253
|
+
'"$common_awk_script"'
|
|
254
|
+
}'
|
|
255
|
+
else
|
|
256
|
+
grepcmd="grep -Eo"
|
|
257
|
+
echoargs="-e"
|
|
258
|
+
awkscript='{
|
|
259
|
+
level = substr($0, 3, 1)
|
|
260
|
+
text = substr($0, match($0, /">.*<\/h/)+2, RLENGTH-5)
|
|
261
|
+
href = substr($0, match($0, '$href_regex')+6, RLENGTH-7)
|
|
262
|
+
'"$common_awk_script"'
|
|
263
|
+
}'
|
|
264
|
+
fi
|
|
265
|
+
|
|
266
|
+
# if closed <h[1-6]> is on the new line, then move it on the prev line
|
|
267
|
+
# for example:
|
|
268
|
+
# was: The command <code>foo1</code>
|
|
269
|
+
# </h1>
|
|
270
|
+
# became: The command <code>foo1</code></h1>
|
|
271
|
+
sed -e ':a' -e 'N' -e '$!ba' -e 's/\n<\/h/<\/h/g' |
|
|
272
|
+
|
|
273
|
+
# Sometimes a line can start with <span>. Fix that.
|
|
274
|
+
sed -e ':a' -e 'N' -e '$!ba' -e 's/\n<span/<span/g' |
|
|
275
|
+
|
|
276
|
+
# find strings that corresponds to template
|
|
277
|
+
$grepcmd '<h.*class="heading-element".*</a' |
|
|
278
|
+
|
|
279
|
+
# remove code tags
|
|
280
|
+
sed 's/<code>//g' | sed 's/<\/code>//g' |
|
|
281
|
+
|
|
282
|
+
# remove g-emoji
|
|
283
|
+
sed 's/<g-emoji[^>]*[^<]*<\/g-emoji> //g' |
|
|
284
|
+
|
|
285
|
+
# now all rows are like:
|
|
286
|
+
# <h1 class="heading-element">title</h1><a href="..."><span>..</span></a>
|
|
287
|
+
# format result line
|
|
288
|
+
# * $0 - whole string
|
|
289
|
+
# * last element of each row: "</hN" where N in (1,2,3,...)
|
|
290
|
+
echo $echoargs "$(awk -v "gh_url=$1" "$awkscript")"
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
# perl -lpE 's/(\[[^\]]*\]\()(.*?)(\))/my ($pre, $in, $post)=($1, $2, $3) ; $in =~ s{\+}{ }g; $in =~ s{%}{\\x}g; $pre.$in.$post/ems')"
|
|
294
|
+
|
|
295
|
+
#
|
|
296
|
+
# Returns filename only from full path or url
|
|
297
|
+
#
|
|
298
|
+
gh_toc_get_filename() {
|
|
299
|
+
echo "${1##*/}"
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
show_version() {
|
|
303
|
+
echo "$gh_toc_version"
|
|
304
|
+
echo
|
|
305
|
+
echo "os: `uname -s`"
|
|
306
|
+
echo "arch: `uname -m`"
|
|
307
|
+
echo "kernel: `uname -r`"
|
|
308
|
+
echo "shell: `$SHELL --version`"
|
|
309
|
+
echo
|
|
310
|
+
for tool in curl wget grep awk sed; do
|
|
311
|
+
printf "%-5s: " $tool
|
|
312
|
+
if type $tool &>/dev/null; then
|
|
313
|
+
$tool --version | head -n 1
|
|
314
|
+
else
|
|
315
|
+
echo "not installed"
|
|
316
|
+
fi
|
|
317
|
+
done
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
show_help() {
|
|
321
|
+
local app_name
|
|
322
|
+
app_name=$(basename "$0")
|
|
323
|
+
echo "GitHub TOC generator ($app_name): $gh_toc_version"
|
|
324
|
+
echo ""
|
|
325
|
+
echo "Usage:"
|
|
326
|
+
echo " $app_name [options] src [src] Create TOC for a README file (url or local path)"
|
|
327
|
+
echo " $app_name - Create TOC for markdown from STDIN"
|
|
328
|
+
echo " $app_name --help Show help"
|
|
329
|
+
echo " $app_name --version Show version"
|
|
330
|
+
echo ""
|
|
331
|
+
echo "Options:"
|
|
332
|
+
echo " --indent <NUM> Set indent size. Default: 3."
|
|
333
|
+
echo " --insert Insert new TOC into original file. For local files only. Default: false."
|
|
334
|
+
echo " See https://github.com/ekalinin/github-markdown-toc/issues/41 for details."
|
|
335
|
+
echo " --no-backup Remove backup file. Set --insert as well. Default: false."
|
|
336
|
+
echo " --hide-footer Do not write date & author of the last TOC update. Set --insert as well. Default: false."
|
|
337
|
+
echo " --skip-header Hide entry of the topmost headlines. Default: false."
|
|
338
|
+
echo " See https://github.com/ekalinin/github-markdown-toc/issues/125 for details."
|
|
339
|
+
echo ""
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
#
|
|
343
|
+
# Options handlers
|
|
344
|
+
#
|
|
345
|
+
gh_toc_app() {
|
|
346
|
+
local need_replace="no"
|
|
347
|
+
local indent=3
|
|
348
|
+
|
|
349
|
+
if [ "$1" = '--help' ] || [ $# -eq 0 ] ; then
|
|
350
|
+
show_help
|
|
351
|
+
return
|
|
352
|
+
fi
|
|
353
|
+
|
|
354
|
+
if [ "$1" = '--version' ]; then
|
|
355
|
+
show_version
|
|
356
|
+
return
|
|
357
|
+
fi
|
|
358
|
+
|
|
359
|
+
if [ "$1" = '--indent' ]; then
|
|
360
|
+
indent="$2"
|
|
361
|
+
shift 2
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
if [ "$1" = "-" ]; then
|
|
365
|
+
if [ -z "$TMPDIR" ]; then
|
|
366
|
+
TMPDIR="/tmp"
|
|
367
|
+
elif [ -n "$TMPDIR" ] && [ ! -d "$TMPDIR" ]; then
|
|
368
|
+
mkdir -p "$TMPDIR"
|
|
369
|
+
fi
|
|
370
|
+
local gh_tmp_md
|
|
371
|
+
if [ "`uname -s`" == "OS/390" ]; then
|
|
372
|
+
local timestamp
|
|
373
|
+
timestamp=$(date +%m%d%Y%H%M%S)
|
|
374
|
+
gh_tmp_md="$TMPDIR/tmp.$timestamp"
|
|
375
|
+
else
|
|
376
|
+
gh_tmp_md=$(mktemp "$TMPDIR/tmp.XXXXXX")
|
|
377
|
+
fi
|
|
378
|
+
while read -r input; do
|
|
379
|
+
echo "$input" >> "$gh_tmp_md"
|
|
380
|
+
done
|
|
381
|
+
gh_toc_md2html "$gh_tmp_md" | gh_toc_grab "" "$indent"
|
|
382
|
+
return
|
|
383
|
+
fi
|
|
384
|
+
|
|
385
|
+
if [ "$1" = '--insert' ]; then
|
|
386
|
+
need_replace="yes"
|
|
387
|
+
shift
|
|
388
|
+
fi
|
|
389
|
+
|
|
390
|
+
if [ "$1" = '--no-backup' ]; then
|
|
391
|
+
need_replace="yes"
|
|
392
|
+
no_backup="yes"
|
|
393
|
+
shift
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
if [ "$1" = '--hide-footer' ]; then
|
|
397
|
+
need_replace="yes"
|
|
398
|
+
no_footer="yes"
|
|
399
|
+
shift
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
if [ "$1" = '--skip-header' ]; then
|
|
403
|
+
skip_header="yes"
|
|
404
|
+
shift
|
|
405
|
+
fi
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
for md in "$@"
|
|
409
|
+
do
|
|
410
|
+
echo ""
|
|
411
|
+
gh_toc "$md" "$#" "$need_replace" "$no_backup" "$no_footer" "$indent" "$skip_header"
|
|
412
|
+
done
|
|
413
|
+
|
|
414
|
+
echo ""
|
|
415
|
+
echo "<!-- Created by https://github.com/ekalinin/github-markdown-toc -->"
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
#
|
|
419
|
+
# Entry point
|
|
420
|
+
#
|
|
421
|
+
gh_toc_app "$@"
|
|
@@ -33,7 +33,7 @@ export async function runTranslation({ appState, options, log }) {
|
|
|
33
33
|
})
|
|
34
34
|
|
|
35
35
|
// Validate provider
|
|
36
|
-
const providerName = options.provider ?? config.provider
|
|
36
|
+
const providerName = (options.provider ?? config.provider)?.toLowerCase()
|
|
37
37
|
if (!VALID_TRANSLATION_PROVIDERS.includes(providerName)) {
|
|
38
38
|
log.E(`Error: Unknown provider "${providerName}". Supported providers: ${VALID_TRANSLATION_PROVIDERS.join(', ')}`)
|
|
39
39
|
process.exit(2)
|
|
@@ -305,7 +305,7 @@ export async function runTranslation({ appState, options, log }) {
|
|
|
305
305
|
}
|
|
306
306
|
], {
|
|
307
307
|
concurrent: false, // Process languages one by one
|
|
308
|
-
...(options.tty ? { renderer: 'simple' } : {}),
|
|
308
|
+
...((options.tty || options.trace || options.debug || options.verbose) ? { renderer: 'simple' } : {}),
|
|
309
309
|
rendererOptions: { collapse: false, clearOutput: false },
|
|
310
310
|
registerSignalListeners: true,
|
|
311
311
|
collapseSubtasks: false
|
|
@@ -395,7 +395,7 @@ export async function processTranslationTask({ appState, taskInfo, listrTask, op
|
|
|
395
395
|
log.V(`Keeping existing translation and hash for ${targetLang}/${key}...`)
|
|
396
396
|
|
|
397
397
|
// Allow the user to directly edit/tweak output key values
|
|
398
|
-
listrTask.output = localizeFormatted({ token: 'msg-no-
|
|
398
|
+
listrTask.output = localizeFormatted({ token: 'msg-no-update-needed-for-key', data: { key }, lang: appState.lang, log })
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
401
|
|
|
@@ -580,7 +580,7 @@ async function translateTextViaProvider({
|
|
|
580
580
|
}
|
|
581
581
|
|
|
582
582
|
if (!errorHandled) {
|
|
583
|
-
log.W(`${providerName} API failed
|
|
583
|
+
log.W(`${providerName} API failed.`, error?.message ?? error)
|
|
584
584
|
}
|
|
585
585
|
}
|
|
586
586
|
}
|
package/src/lib/consts.js
CHANGED
|
@@ -6,12 +6,14 @@ export const LANGTAG_DEFAULT = LANGTAG_ENGLISH
|
|
|
6
6
|
|
|
7
7
|
export const VALID_TRANSLATION_PROVIDERS = [
|
|
8
8
|
'anthropic',
|
|
9
|
+
'google',
|
|
9
10
|
'openai'
|
|
10
11
|
]
|
|
11
12
|
|
|
12
13
|
export const ENV_VARS = [
|
|
13
14
|
{ name: 'ANTHROPIC_API_KEY', description: 'Your Anthropic API key' },
|
|
14
15
|
{ name: 'OPENAI_API_KEY', description: 'Your OpenAI API key' },
|
|
16
|
+
{ name: 'GOOGLE_API_KEY', description: 'Your Google Gemini API key' },
|
|
15
17
|
{ name: 'ALT_LANGUAGE', description: 'POSIX locale used for display' }
|
|
16
18
|
]
|
|
17
19
|
|
|
@@ -30,6 +32,7 @@ export const SUPPORTED_REFERENCE_FILE_EXTENSIONS = [
|
|
|
30
32
|
|
|
31
33
|
export const DEFAULT_LLM_MODELS = {
|
|
32
34
|
anthropic: 'claude-3-7-sonnet-20250219',
|
|
35
|
+
google: 'gemini-2.0-flash',
|
|
33
36
|
openai: 'gpt-4-turbo'
|
|
34
37
|
}
|
|
35
38
|
|
package/src/main.mjs
CHANGED
|
@@ -125,9 +125,9 @@ export async function run() {
|
|
|
125
125
|
.option('-cp, --context-prefix <value>', `String to be prefixed to all keys to search for additional context, which are passed along to the AI for context`)
|
|
126
126
|
.option('-cs, --context-suffix <value>', `String to be suffixed to all keys to search for additional context, which are passed along to the AI for context`)
|
|
127
127
|
.option('-L, --look-for-context-data', `If specified, ALT will pass any context data specified in the reference file to the AI provider for translation. At least one of --contextPrefix or --contextSuffix must be specified`, false)
|
|
128
|
-
.option('-v, --verbose', `Enables verbose spew`, false)
|
|
129
|
-
.option('-d, --debug', `Enables debug spew`, false)
|
|
130
|
-
.option('-t, --trace', `Enables trace spew`, false)
|
|
128
|
+
.option('-v, --verbose', `Enables verbose spew; forces --tty mode`, false)
|
|
129
|
+
.option('-d, --debug', `Enables debug spew; forces --tty mode`, false)
|
|
130
|
+
.option('-t, --trace', `Enables trace spew; forces --tty mode`, false)
|
|
131
131
|
.option('--dev', `Enable dev mode, which prints stack traces with errors`, false)
|
|
132
132
|
.hook('preAction', cmd => {
|
|
133
133
|
const opts = cmd.opts()
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export function name() {
|
|
2
|
+
return 'Google'
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export async function listModels(apiKey) {
|
|
6
|
+
let allModels = []
|
|
7
|
+
let pageToken = null
|
|
8
|
+
let hasMore = true
|
|
9
|
+
|
|
10
|
+
while (hasMore) {
|
|
11
|
+
const url = new URL('https://generativelanguage.googleapis.com/v1/models')
|
|
12
|
+
|
|
13
|
+
// Add API key as query parameter
|
|
14
|
+
url.searchParams.append('key', apiKey)
|
|
15
|
+
|
|
16
|
+
// Add page token if we have one
|
|
17
|
+
if (pageToken) {
|
|
18
|
+
url.searchParams.append('pageToken', pageToken)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const response = await fetch(url, {
|
|
22
|
+
method: 'GET',
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'application/json'
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const result = await response.json()
|
|
29
|
+
|
|
30
|
+
if (result.models && result.models.length > 0) {
|
|
31
|
+
allModels = [
|
|
32
|
+
...allModels,
|
|
33
|
+
...result.models
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
// Check if there's another page
|
|
37
|
+
if (result.nextPageToken) {
|
|
38
|
+
pageToken = result.nextPageToken
|
|
39
|
+
} else {
|
|
40
|
+
hasMore = false
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
hasMore = false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { models: allModels }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getTranslationRequestDetails({ model, messages, apiKey, log }) {
|
|
51
|
+
return {
|
|
52
|
+
url: `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`,
|
|
53
|
+
params: {
|
|
54
|
+
contents: messages.map(m => ({
|
|
55
|
+
role: 'user',
|
|
56
|
+
parts: [ { text: m } ]
|
|
57
|
+
}))
|
|
58
|
+
},
|
|
59
|
+
config: {
|
|
60
|
+
headers: {
|
|
61
|
+
'Content-Type': 'application/json'
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getResult(response, log) {
|
|
68
|
+
return response.data.candidates?.[0]?.content?.parts?.[0]?.text?.trim?.() || ''
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getHeader(headers, name) {
|
|
72
|
+
return headers[name] || headers.get?.(name)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getSleepInterval(headers, log) {
|
|
76
|
+
log.T(headers)
|
|
77
|
+
const retryAfter = parseInt(getHeader(headers, 'retry-after'))
|
|
78
|
+
log.D('retryAfter', retryAfter)
|
|
79
|
+
return isNaN(retryAfter) ? 0 : 1000 * retryAfter + 200
|
|
80
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
'msg-nothing-to-do': `Nothing to do`,
|
|
3
|
+
'msg-finished-with-errors': `Finished with %%errorsEncountered%% error%%s%%`,
|
|
4
|
+
|
|
5
|
+
'msg-translating': 'Translating...',
|
|
6
|
+
'msg-translating-key': `Translating %%key%%`,
|
|
7
|
+
'msg-preparing-endpoint-config': `Preparing endpoint configuration...`,
|
|
8
|
+
'msg-hitting-provider-endpoint': `Hitting %%providerName%% endpoint%%attemptStr%%...`,
|
|
9
|
+
|
|
10
|
+
'msg-no-update-needed-for-key': `No update needed for %%key%%`,
|
|
11
|
+
'msg-rate-limited-sleeping': `Rate limited; sleeping for %%interval%%s...%%attemptStr%%`,
|
|
12
|
+
'msg-show-translation-result': `Translated %%key%%: "%%newValue%%"`,
|
|
13
|
+
'msg-processing-lang-and-key': `[%%progress%%%] Processing %%targetLang%% – %%key%%...`,
|
|
14
|
+
|
|
15
|
+
'msg-translation-reason-forced': `Forced update`,
|
|
16
|
+
'msg-translation-reason-outputFileDidNotExist': `Output file %%outputFile%% did not exist`,
|
|
17
|
+
'msg-translation-reason-userMissingReferenceValueHash': `No reference hash found`,
|
|
18
|
+
'msg-translation-reason-userModifiedReferenceValue': `User modified reference string`,
|
|
19
|
+
'msg-translation-reason-missingOutputKey': `No existing translation found`,
|
|
20
|
+
'msg-translation-reason-missingOutputValueHash': `No hash found in cache file`,
|
|
21
|
+
|
|
22
|
+
'error-value-not-a-string': `Value for reference key "%%key%%" was "%%type%%". Expected a string! Skipping...`,
|
|
23
|
+
'error-value-not-in-reference-data': `Key "%%key%%" did not exist in reference file`,
|
|
24
|
+
'error-translation-failed': `Translation failed for target language=%%targetLang%%; key=%%key%%; text=%%refValue%%`,
|
|
25
|
+
'error-bad-reference-file-ext': `Unsupported file type for reference file "%%ext%%"`,
|
|
26
|
+
'error-reference-var-not-found-in-data': `Couldn't find "%%referenceExportedVarName%%" in reference file "%%referenceFile%%". Did you mean one of these instead?: %%possibleKeys%%`,
|
|
27
|
+
'error-reference-file-load-failed': `Failed to load reference file "%%referenceFile%%"`
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"msg-nothing-to-do": "Nothing to do",
|
|
3
|
+
"msg-finished-with-errors": "Finished with %%errorsEncountered%% error%%s%%",
|
|
4
|
+
|
|
5
|
+
"msg-translating": "Translating...",
|
|
6
|
+
"msg-translating-key": "Translating %%key%%",
|
|
7
|
+
"msg-preparing-endpoint-config": "Preparing endpoint configuration...",
|
|
8
|
+
"msg-hitting-provider-endpoint": "Hitting %%providerName%% endpoint%%attemptStr%%...",
|
|
9
|
+
|
|
10
|
+
"msg-no-update-needed-for-key": "No update needed for %%key%%",
|
|
11
|
+
"msg-rate-limited-sleeping": "Rate limited; sleeping for %%interval%%s...%%attemptStr%%",
|
|
12
|
+
"msg-show-translation-result": "Translated %%key%%: \"%%newValue%%\"",
|
|
13
|
+
"msg-processing-lang-and-key": "[%%progress%%%] Processing %%targetLang%% – %%key%%...",
|
|
14
|
+
|
|
15
|
+
"msg-translation-reason-forced": "Forced update",
|
|
16
|
+
"msg-translation-reason-outputFileDidNotExist": "Output file %%outputFile%% did not exist",
|
|
17
|
+
"msg-translation-reason-userMissingReferenceValueHash": "No reference hash found",
|
|
18
|
+
"msg-translation-reason-userModifiedReferenceValue": "User modified reference string",
|
|
19
|
+
"msg-translation-reason-missingOutputKey": "No existing translation found",
|
|
20
|
+
"msg-translation-reason-missingOutputValueHash": "No hash found in cache file",
|
|
21
|
+
|
|
22
|
+
"error-value-not-a-string": "Value for reference key \"%%key%%\" was \"%%type%%\". Expected a string! Skipping...",
|
|
23
|
+
"error-value-not-in-reference-data": "Key \"%%key%%\" did not exist in reference file",
|
|
24
|
+
"error-translation-failed": "Translation failed for target language=%%targetLang%%; key=%%key%%; text=%%refValue%%",
|
|
25
|
+
"error-bad-reference-file-ext": "Unsupported file type for reference file \"%%ext%%\"",
|
|
26
|
+
"error-reference-var-not-found-in-data": "Couldn't find \"%%referenceExportedVarName%%\" in reference file \"%%referenceFile%%\". Did you mean one of these instead?: %%possibleKeys%%",
|
|
27
|
+
"error-reference-file-load-failed": "Failed to load reference file \"%%referenceFile%%\""
|
|
28
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"msg-nothing-to-do": "Nothing to do",
|
|
3
|
+
"msg-finished-with-errors": "Finished with %%errorsEncountered%% error%%s%%",
|
|
4
|
+
|
|
5
|
+
"msg-translating": "Translating...",
|
|
6
|
+
"msg-translating-key": "Translating %%key%%",
|
|
7
|
+
"msg-preparing-endpoint-config": "Preparing endpoint configuration...",
|
|
8
|
+
"msg-hitting-provider-endpoint": "Hitting %%providerName%% endpoint%%attemptStr%%...",
|
|
9
|
+
|
|
10
|
+
"msg-no-update-needed-for-key": "No update needed for %%key%%",
|
|
11
|
+
"msg-rate-limited-sleeping": "Rate limited; sleeping for %%interval%%s...%%attemptStr%%",
|
|
12
|
+
"msg-show-translation-result": "Translated %%key%%: \"%%newValue%%\"",
|
|
13
|
+
"msg-processing-lang-and-key": "[%%progress%%%] Processing %%targetLang%% – %%key%%...",
|
|
14
|
+
|
|
15
|
+
"msg-translation-reason-forced": "Forced update",
|
|
16
|
+
"msg-translation-reason-outputFileDidNotExist": "Output file %%outputFile%% did not exist",
|
|
17
|
+
"msg-translation-reason-userMissingReferenceValueHash": "No reference hash found",
|
|
18
|
+
"msg-translation-reason-userModifiedReferenceValue": "User modified reference string",
|
|
19
|
+
"msg-translation-reason-missingOutputKey": "No existing translation found",
|
|
20
|
+
"msg-translation-reason-missingOutputValueHash": "No hash found in cache file",
|
|
21
|
+
|
|
22
|
+
// Errors
|
|
23
|
+
"error-value-not-a-string": "Value for reference key \"%%key%%\" was \"%%type%%\". Expected a string! Skipping...",
|
|
24
|
+
"error-value-not-in-reference-data": "Key \"%%key%%\" did not exist in reference file",
|
|
25
|
+
"error-translation-failed": "Translation failed for target language=%%targetLang%%; key=%%key%%; text=%%refValue%%",
|
|
26
|
+
"error-bad-reference-file-ext": "Unsupported file type for reference file \"%%ext%%\"",
|
|
27
|
+
"error-reference-var-not-found-in-data": "Couldn't find \"%%referenceExportedVarName%%\" in reference file \"%%referenceFile%%\". Did you mean one of these instead?: %%possibleKeys%%",
|
|
28
|
+
"error-reference-file-load-failed": "Failed to load reference file \"%%referenceFile%%\""
|
|
29
|
+
}
|