@verbaly/studio 0.56.1 → 0.57.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 +2 -40
- package/dist/cli.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ui/assets/{index-3A7RArYB.css → index-B2gDPqi5.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/package.json +5 -27
- package/dist/panel/Actions.svelte +0 -255
- package/dist/panel/Health.svelte +0 -184
- package/dist/panel/Marks.svelte +0 -56
- package/dist/panel/Panel.svelte +0 -337
- package/dist/panel/Panel.svelte.d.ts +0 -12
- package/dist/panel/Rail.svelte +0 -303
- package/dist/panel/Row.svelte +0 -340
- package/dist/panel/api.ts +0 -82
- package/dist/panel/index.ts +0 -18
- package/dist/panel/model.ts +0 -163
- package/dist/panel/tokens.css +0 -106
- package/dist/panel/validate.ts +0 -94
- package/dist/panel/wire.ts +0 -29
- package/dist/panel/words.ts +0 -59
- /package/dist/ui/assets/{index-DlWGcJoP.js → index-3Geaeu_2.js} +0 -0
package/dist/panel/validate.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { parse, parseTags } from 'verbaly';
|
|
2
|
-
|
|
3
|
-
// The browser preview of the gate, on the runtime's own parser: the server is still the authority.
|
|
4
|
-
export interface Preview {
|
|
5
|
-
params: { name: string; inSource: boolean; inTarget: boolean }[];
|
|
6
|
-
problems: string[];
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
type Node = { kind: string; name?: string; variants?: [string, Node[]][] };
|
|
10
|
-
|
|
11
|
-
function walk(nodes: Node[], out: Map<string, Set<string>>): void {
|
|
12
|
-
for (const node of nodes) {
|
|
13
|
-
if (node.kind === 'param' && node.name) {
|
|
14
|
-
if (!out.has(node.name)) out.set(node.name, new Set());
|
|
15
|
-
for (const [selector, body] of node.variants ?? []) {
|
|
16
|
-
out.get(node.name)!.add(selector);
|
|
17
|
-
walk(body, out);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function shape(message: string): Map<string, Set<string>> {
|
|
24
|
-
const out = new Map<string, Set<string>>();
|
|
25
|
-
try {
|
|
26
|
-
walk(parse(message) as Node[], out);
|
|
27
|
-
} catch {
|
|
28
|
-
// a half-typed message is not a defect, so an unparseable draft simply reports nothing yet
|
|
29
|
-
}
|
|
30
|
-
return out;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
type Token = string | { name: string; children: Token[] };
|
|
34
|
-
|
|
35
|
-
function collect(tokens: Token[], out: string[]): void {
|
|
36
|
-
for (const token of tokens) {
|
|
37
|
-
if (typeof token === 'string') continue;
|
|
38
|
-
out.push(token.name);
|
|
39
|
-
collect(token.children, out);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function tags(message: string): string[] {
|
|
44
|
-
const out: string[] = [];
|
|
45
|
-
try {
|
|
46
|
-
collect(parseTags(message) as Token[], out);
|
|
47
|
-
} catch {
|
|
48
|
-
return out;
|
|
49
|
-
}
|
|
50
|
-
return out;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function counted(list: string[]): Map<string, number> {
|
|
54
|
-
const out = new Map<string, number>();
|
|
55
|
-
for (const item of list) out.set(item, (out.get(item) ?? 0) + 1);
|
|
56
|
-
return out;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const list = (names: string[]) =>
|
|
60
|
-
names.length === 1 ? names[0] : `${names.slice(0, -1).join(', ')} and ${names.at(-1)}`;
|
|
61
|
-
|
|
62
|
-
export function preview(source: string, translated: string): Preview {
|
|
63
|
-
const from = shape(source);
|
|
64
|
-
const to = shape(translated);
|
|
65
|
-
const params = [...new Set([...from.keys(), ...to.keys()])].sort().map((name) => ({
|
|
66
|
-
name,
|
|
67
|
-
inSource: from.has(name),
|
|
68
|
-
inTarget: to.has(name),
|
|
69
|
-
}));
|
|
70
|
-
|
|
71
|
-
const problems: string[] = [];
|
|
72
|
-
const lost = params.filter((p) => p.inSource && !p.inTarget).map((p) => `{${p.name}}`);
|
|
73
|
-
const made = params.filter((p) => !p.inSource && p.inTarget).map((p) => `{${p.name}}`);
|
|
74
|
-
if (lost.length) problems.push(`${list(lost)} is missing, so its value never reaches the text`);
|
|
75
|
-
if (made.length) problems.push(`${list(made)} is not in the source, so it renders as literal text`);
|
|
76
|
-
|
|
77
|
-
// a plural block that lost its catch-all renders nothing for any count it does not list
|
|
78
|
-
for (const [name, selectors] of to) {
|
|
79
|
-
if (selectors.size && !selectors.has('other')) {
|
|
80
|
-
problems.push(`the block for {${name}} has no "other" case, so some counts render empty`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const sourceTags = counted(tags(source));
|
|
85
|
-
const targetTags = counted(tags(translated));
|
|
86
|
-
for (const [name, n] of sourceTags) {
|
|
87
|
-
if ((targetTags.get(name) ?? 0) < n) problems.push(`<${name}> is missing from the translation`);
|
|
88
|
-
}
|
|
89
|
-
for (const [name, n] of targetTags) {
|
|
90
|
-
if ((sourceTags.get(name) ?? 0) < n) problems.push(`<${name}> is not in the source message`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return { params, problems };
|
|
94
|
-
}
|
package/dist/panel/wire.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// Every route answers JSON and every failure has to read like a sentence, so it is asked once here.
|
|
2
|
-
export interface Answer<T> {
|
|
3
|
-
value?: T;
|
|
4
|
-
error?: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export async function ask<T>(
|
|
8
|
-
token: string,
|
|
9
|
-
path: string,
|
|
10
|
-
// an intersection narrows body, it does not replace it, so the fetch shape is omitted first
|
|
11
|
-
init?: Omit<RequestInit, 'body'> & { body?: unknown },
|
|
12
|
-
): Promise<Answer<T>> {
|
|
13
|
-
const join = path.includes('?') ? '&' : '?';
|
|
14
|
-
let response: Response;
|
|
15
|
-
try {
|
|
16
|
-
response = await fetch(`${path}${join}t=${encodeURIComponent(token)}`, {
|
|
17
|
-
...init,
|
|
18
|
-
headers: init?.body === undefined ? undefined : { 'content-type': 'application/json' },
|
|
19
|
-
body: init?.body === undefined ? undefined : JSON.stringify(init.body),
|
|
20
|
-
});
|
|
21
|
-
} catch {
|
|
22
|
-
return { error: '[verbaly] the studio server is not answering, is it still running?' };
|
|
23
|
-
}
|
|
24
|
-
const body = (await response.json().catch(() => ({}))) as T & { error?: string };
|
|
25
|
-
if (!response.ok) {
|
|
26
|
-
return { error: body.error ?? `[verbaly] the server answered ${response.status}` };
|
|
27
|
-
}
|
|
28
|
-
return { value: body };
|
|
29
|
-
}
|
package/dist/panel/words.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import type { MessageState } from './model';
|
|
2
|
-
|
|
3
|
-
// The words are a product decision: each names a checkable fact, never a stage of a workflow.
|
|
4
|
-
export const STATE: Record<MessageState, { short: string; long: string }> = {
|
|
5
|
-
done: { short: 'ready', long: 'ready' },
|
|
6
|
-
draft: { short: 'unread', long: 'written by a machine, nobody has read it' },
|
|
7
|
-
missing: { short: 'missing', long: 'missing, so your visitor sees the source language' },
|
|
8
|
-
broken: { short: 'breaks', long: 'breaks your site' },
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export const FILTER: Record<'all' | 'look' | 'missing' | 'draft' | 'broken', string> = {
|
|
12
|
-
all: 'everything',
|
|
13
|
-
missing: 'missing',
|
|
14
|
-
draft: 'unread',
|
|
15
|
-
broken: 'breaks your site',
|
|
16
|
-
look: 'worth a look',
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// The signals do not score a translation, they point at the few worth reading first.
|
|
20
|
-
export const SIGNAL: Record<string, string> = {
|
|
21
|
-
divergent: 'the source says this two ways',
|
|
22
|
-
collision: 'came back as two translations',
|
|
23
|
-
echo: 'still identical to the source',
|
|
24
|
-
digits: 'the numbers moved',
|
|
25
|
-
url: 'a link changed',
|
|
26
|
-
code: 'a code span changed',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const MARK_AS_READ = 'Mark as read';
|
|
30
|
-
export const UNDO = 'Undo';
|
|
31
|
-
|
|
32
|
-
// The two commands the panel can run, named by what they leave behind, not by what they are called.
|
|
33
|
-
export const ACTION = {
|
|
34
|
-
find: 'Find new text',
|
|
35
|
-
findWhy: 'Reads your code and adds what your catalogs do not have yet.',
|
|
36
|
-
translate: 'Translate what is missing',
|
|
37
|
-
translateWhy: 'Calls your provider, so it costs money and takes minutes.',
|
|
38
|
-
cancel: 'Not now',
|
|
39
|
-
working: 'Reading your code',
|
|
40
|
-
running: 'Translating',
|
|
41
|
-
nothingNew: 'Nothing new: your catalogs already have every message your code uses.',
|
|
42
|
-
nothingMissing: 'Nothing missing in the languages you ticked.',
|
|
43
|
-
pickFirst: 'Tick a language to translate it.',
|
|
44
|
-
onlyLocale: 'This project has one language, so there is nothing to translate into.',
|
|
45
|
-
scanOff: 'Source scanning is off in your config, so there is nothing to read.',
|
|
46
|
-
} as const;
|
|
47
|
-
|
|
48
|
-
// An empty screen that tells you to click something that is not there is worse than a blank one.
|
|
49
|
-
export const EMPTY = {
|
|
50
|
-
noCatalogs: 'No messages yet. Studio reads the catalogs in your project, and there are none here.',
|
|
51
|
-
oneLocale: 'One language is all you have, so there is nothing to compare it against yet.',
|
|
52
|
-
oneLocaleFix: 'Add another one to your config and it shows up in the rail.',
|
|
53
|
-
pickOne: 'Tick a language in the rail to see its translations.',
|
|
54
|
-
noMatch: 'Nothing matches. Every message here is in the state you asked for.',
|
|
55
|
-
} as const;
|
|
56
|
-
|
|
57
|
-
export function messages(n: number): string {
|
|
58
|
-
return `${n} ${n === 1 ? 'message' : 'messages'}`;
|
|
59
|
-
}
|
|
File without changes
|