@lp-labs/translation-workflow 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +70 -2
- package/dist/cjs/index.d.ts +28 -0
- package/dist/cjs/index.js +106 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/index.d.ts +28 -0
- package/dist/esm/index.js +102 -0
- package/package.json +61 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Libin Prasanth
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,71 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @lp-labs/translation-workflow
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Bounded translation catalog comparison with sorted key changes, simple named-placeholder
|
|
4
|
+
compatibility checks, and validated review metadata.
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
import { diffTranslationCatalogs, createTranslationReview } from '@lp-labs/translation-workflow';
|
|
8
|
+
|
|
9
|
+
const diff = diffTranslationCatalogs(
|
|
10
|
+
{ greeting: 'Hello, {name}!' },
|
|
11
|
+
{ greeting: 'Bonjour, {name}!', newer: 'Bienvenue' },
|
|
12
|
+
);
|
|
13
|
+
console.log(diff.compatible, diff.added, diff.changed);
|
|
14
|
+
console.log(
|
|
15
|
+
createTranslationReview({
|
|
16
|
+
locale: 'fr-FR',
|
|
17
|
+
key: 'greeting',
|
|
18
|
+
reviewer: 'editor-1',
|
|
19
|
+
status: 'approved',
|
|
20
|
+
reviewedAt: '2026-11-05T08:00:00Z',
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npm install @lp-labs/translation-workflow
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## API
|
|
32
|
+
|
|
33
|
+
- `diffTranslationCatalogs(source, target): TranslationCatalogDiff` compares own string entries in
|
|
34
|
+
two `TranslationCatalog` objects. `added` are target-only keys; `missing` are source-only keys;
|
|
35
|
+
`changed` are shared keys with different text; `placeholderMismatches` are shared keys whose
|
|
36
|
+
`{name}` placeholder multisets differ. The frozen result's `compatible` flag is false only when
|
|
37
|
+
at least one shared key has a placeholder mismatch.
|
|
38
|
+
- `TranslationCatalog` is a string-keyed catalog type.
|
|
39
|
+
- `TranslationCatalogDiff` exposes readonly sorted arrays `added`, `missing`, `changed`, and
|
|
40
|
+
`placeholderMismatches`, plus `compatible`.
|
|
41
|
+
- `createTranslationReview(input: TranslationReviewInput): TranslationReview` validates and freezes
|
|
42
|
+
a review record. Required fields are `locale`, `key`, `reviewer`, `status`, and `reviewedAt`;
|
|
43
|
+
statuses are `draft`, `reviewed`, `approved`, or `rejected`. Optional `note` is limited to 2000
|
|
44
|
+
characters. Timestamps must be ISO UTC strings ending in `Z`.
|
|
45
|
+
- `TranslationReviewInput` and `TranslationReview` describe the review input and frozen output.
|
|
46
|
+
|
|
47
|
+
Catalogs must be plain own-property records with at most 10,000 entries, keys from 1–256
|
|
48
|
+
characters, and string values of at most 10,000 characters. Reviews use plain own enumerable data
|
|
49
|
+
properties only; locale, key, and reviewer each have a 1–256 character limit. Unknown fields,
|
|
50
|
+
accessors, symbols, and inherited records are rejected. Invalid values throw `TypeError` or
|
|
51
|
+
`RangeError`.
|
|
52
|
+
|
|
53
|
+
## Customization and limits
|
|
54
|
+
|
|
55
|
+
Use the diff arrays to gate a human review or to display a bounded change list; store returned review
|
|
56
|
+
records in your own persistence layer. Placeholder recognition is intentionally limited to simple
|
|
57
|
+
`{name}` identifiers (letters followed by letters, digits, `_`, `.`, or `-`). This utility does not
|
|
58
|
+
extract strings from source code, parse ICU MessageFormat, validate plural/select branches, or
|
|
59
|
+
replace a translation platform. Catalog ordering is deterministic and independent of insertion
|
|
60
|
+
order.
|
|
61
|
+
|
|
62
|
+
Package scripts: `npm run build`, `npm run typecheck`, `npm test`, `npm run format`, and
|
|
63
|
+
`npm run format:check`. Requires Node.js 18 or newer. There are no runtime or peer dependencies.
|
|
64
|
+
|
|
65
|
+
## Support
|
|
66
|
+
|
|
67
|
+
If this library is useful to you, consider supporting its maintenance with a [donation via PayPal](https://www.paypal.me/LibinPrasanth).
|
|
68
|
+
|
|
69
|
+
## Author
|
|
70
|
+
|
|
71
|
+
Created by [Libin Prasanth](https://www.linkedin.com/in/libinprasanth/). Connect on LinkedIn for updates and discussion.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface TranslationCatalog {
|
|
2
|
+
readonly [key: string]: string;
|
|
3
|
+
}
|
|
4
|
+
export interface TranslationCatalogDiff {
|
|
5
|
+
readonly added: readonly string[];
|
|
6
|
+
readonly missing: readonly string[];
|
|
7
|
+
readonly changed: readonly string[];
|
|
8
|
+
readonly placeholderMismatches: readonly string[];
|
|
9
|
+
readonly compatible: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface TranslationReviewInput {
|
|
12
|
+
locale: string;
|
|
13
|
+
key: string;
|
|
14
|
+
reviewer: string;
|
|
15
|
+
status: 'draft' | 'reviewed' | 'approved' | 'rejected';
|
|
16
|
+
reviewedAt: string;
|
|
17
|
+
note?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface TranslationReview {
|
|
20
|
+
readonly locale: string;
|
|
21
|
+
readonly key: string;
|
|
22
|
+
readonly reviewer: string;
|
|
23
|
+
readonly status: 'draft' | 'reviewed' | 'approved' | 'rejected';
|
|
24
|
+
readonly reviewedAt: string;
|
|
25
|
+
readonly note?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function diffTranslationCatalogs(source: TranslationCatalog, target: TranslationCatalog): TranslationCatalogDiff;
|
|
28
|
+
export declare function createTranslationReview(input: TranslationReviewInput): TranslationReview;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.diffTranslationCatalogs = diffTranslationCatalogs;
|
|
4
|
+
exports.createTranslationReview = createTranslationReview;
|
|
5
|
+
function catalogEntries(value) {
|
|
6
|
+
if (value === null ||
|
|
7
|
+
typeof value !== 'object' ||
|
|
8
|
+
Array.isArray(value) ||
|
|
9
|
+
(Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)) {
|
|
10
|
+
throw new TypeError('Catalogs must be plain string-keyed objects.');
|
|
11
|
+
}
|
|
12
|
+
const keys = Reflect.ownKeys(value);
|
|
13
|
+
if (keys.length > 10_000)
|
|
14
|
+
throw new RangeError('Catalogs are limited to 10000 entries.');
|
|
15
|
+
const entries = new Map();
|
|
16
|
+
for (const key of keys) {
|
|
17
|
+
const descriptor = typeof key === 'string' ? Object.getOwnPropertyDescriptor(value, key) : undefined;
|
|
18
|
+
if (typeof key !== 'string' ||
|
|
19
|
+
!descriptor?.enumerable ||
|
|
20
|
+
!('value' in descriptor) ||
|
|
21
|
+
typeof descriptor.value !== 'string') {
|
|
22
|
+
throw new TypeError('Catalog entries must be enumerable string data properties.');
|
|
23
|
+
}
|
|
24
|
+
if (!key || key.length > 256 || descriptor.value.length > 10_000) {
|
|
25
|
+
throw new RangeError('Catalog keys are limited to 1..256 characters and values to 10000.');
|
|
26
|
+
}
|
|
27
|
+
entries.set(key, descriptor.value);
|
|
28
|
+
}
|
|
29
|
+
return entries;
|
|
30
|
+
}
|
|
31
|
+
function placeholders(text) {
|
|
32
|
+
return [...text.matchAll(/\{([A-Za-z][A-Za-z0-9_.-]{0,127})\}/g)]
|
|
33
|
+
.map((match) => match[1])
|
|
34
|
+
.sort();
|
|
35
|
+
}
|
|
36
|
+
function sameStrings(first, second) {
|
|
37
|
+
return first.length === second.length && first.every((value, index) => value === second[index]);
|
|
38
|
+
}
|
|
39
|
+
function diffTranslationCatalogs(source, target) {
|
|
40
|
+
const sourceEntries = catalogEntries(source);
|
|
41
|
+
const targetEntries = catalogEntries(target);
|
|
42
|
+
const added = [...targetEntries.keys()].filter((key) => !sourceEntries.has(key)).sort();
|
|
43
|
+
const missing = [...sourceEntries.keys()].filter((key) => !targetEntries.has(key)).sort();
|
|
44
|
+
const shared = [...sourceEntries.keys()].filter((key) => targetEntries.has(key)).sort();
|
|
45
|
+
const changed = shared.filter((key) => sourceEntries.get(key) !== targetEntries.get(key));
|
|
46
|
+
const placeholderMismatches = shared.filter((key) => !sameStrings(placeholders(sourceEntries.get(key)), placeholders(targetEntries.get(key))));
|
|
47
|
+
return Object.freeze({
|
|
48
|
+
added: Object.freeze(added),
|
|
49
|
+
missing: Object.freeze(missing),
|
|
50
|
+
changed: Object.freeze(changed),
|
|
51
|
+
placeholderMismatches: Object.freeze(placeholderMismatches),
|
|
52
|
+
compatible: placeholderMismatches.length === 0,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function createTranslationReview(input) {
|
|
56
|
+
if (input === null ||
|
|
57
|
+
typeof input !== 'object' ||
|
|
58
|
+
Array.isArray(input) ||
|
|
59
|
+
(Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null)) {
|
|
60
|
+
throw new TypeError('Review metadata must be a plain object.');
|
|
61
|
+
}
|
|
62
|
+
const allowed = ['locale', 'key', 'reviewer', 'status', 'reviewedAt', 'note'];
|
|
63
|
+
const values = {};
|
|
64
|
+
for (const property of Reflect.ownKeys(input)) {
|
|
65
|
+
const descriptor = typeof property === 'string' ? Object.getOwnPropertyDescriptor(input, property) : undefined;
|
|
66
|
+
if (typeof property !== 'string' ||
|
|
67
|
+
!allowed.includes(property) ||
|
|
68
|
+
!descriptor?.enumerable ||
|
|
69
|
+
!('value' in descriptor)) {
|
|
70
|
+
throw new TypeError('Review metadata contains an unsupported or non-data property.');
|
|
71
|
+
}
|
|
72
|
+
values[property] = descriptor.value;
|
|
73
|
+
}
|
|
74
|
+
for (const key of ['locale', 'key', 'reviewer', 'status', 'reviewedAt']) {
|
|
75
|
+
if (!Object.hasOwn(values, key))
|
|
76
|
+
throw new TypeError(`Review field "${key}" is required.`);
|
|
77
|
+
}
|
|
78
|
+
for (const key of ['locale', 'key', 'reviewer']) {
|
|
79
|
+
const value = values[key];
|
|
80
|
+
if (typeof value !== 'string' || value.length < 1 || value.length > 256) {
|
|
81
|
+
throw new RangeError(`${key} must contain 1..256 characters.`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!['draft', 'reviewed', 'approved', 'rejected'].includes(values.status)) {
|
|
85
|
+
throw new TypeError('status must be draft, reviewed, approved, or rejected.');
|
|
86
|
+
}
|
|
87
|
+
const timestamp = typeof values.reviewedAt === 'string' &&
|
|
88
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/.test(values.reviewedAt)
|
|
89
|
+
? Date.parse(values.reviewedAt)
|
|
90
|
+
: Number.NaN;
|
|
91
|
+
if (!Number.isFinite(timestamp) ||
|
|
92
|
+
new Date(timestamp).toISOString().slice(0, 19) !== values.reviewedAt.slice(0, 19)) {
|
|
93
|
+
throw new TypeError('reviewedAt must be an ISO timestamp ending in Z.');
|
|
94
|
+
}
|
|
95
|
+
if (values.note !== undefined && (typeof values.note !== 'string' || values.note.length > 2000)) {
|
|
96
|
+
throw new RangeError('note must be a string of at most 2000 characters.');
|
|
97
|
+
}
|
|
98
|
+
return Object.freeze({
|
|
99
|
+
locale: values.locale,
|
|
100
|
+
key: values.key,
|
|
101
|
+
reviewer: values.reviewer,
|
|
102
|
+
status: values.status,
|
|
103
|
+
reviewedAt: values.reviewedAt,
|
|
104
|
+
...(values.note === undefined ? {} : { note: values.note }),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface TranslationCatalog {
|
|
2
|
+
readonly [key: string]: string;
|
|
3
|
+
}
|
|
4
|
+
export interface TranslationCatalogDiff {
|
|
5
|
+
readonly added: readonly string[];
|
|
6
|
+
readonly missing: readonly string[];
|
|
7
|
+
readonly changed: readonly string[];
|
|
8
|
+
readonly placeholderMismatches: readonly string[];
|
|
9
|
+
readonly compatible: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface TranslationReviewInput {
|
|
12
|
+
locale: string;
|
|
13
|
+
key: string;
|
|
14
|
+
reviewer: string;
|
|
15
|
+
status: 'draft' | 'reviewed' | 'approved' | 'rejected';
|
|
16
|
+
reviewedAt: string;
|
|
17
|
+
note?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface TranslationReview {
|
|
20
|
+
readonly locale: string;
|
|
21
|
+
readonly key: string;
|
|
22
|
+
readonly reviewer: string;
|
|
23
|
+
readonly status: 'draft' | 'reviewed' | 'approved' | 'rejected';
|
|
24
|
+
readonly reviewedAt: string;
|
|
25
|
+
readonly note?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function diffTranslationCatalogs(source: TranslationCatalog, target: TranslationCatalog): TranslationCatalogDiff;
|
|
28
|
+
export declare function createTranslationReview(input: TranslationReviewInput): TranslationReview;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
function catalogEntries(value) {
|
|
2
|
+
if (value === null ||
|
|
3
|
+
typeof value !== 'object' ||
|
|
4
|
+
Array.isArray(value) ||
|
|
5
|
+
(Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)) {
|
|
6
|
+
throw new TypeError('Catalogs must be plain string-keyed objects.');
|
|
7
|
+
}
|
|
8
|
+
const keys = Reflect.ownKeys(value);
|
|
9
|
+
if (keys.length > 10_000)
|
|
10
|
+
throw new RangeError('Catalogs are limited to 10000 entries.');
|
|
11
|
+
const entries = new Map();
|
|
12
|
+
for (const key of keys) {
|
|
13
|
+
const descriptor = typeof key === 'string' ? Object.getOwnPropertyDescriptor(value, key) : undefined;
|
|
14
|
+
if (typeof key !== 'string' ||
|
|
15
|
+
!descriptor?.enumerable ||
|
|
16
|
+
!('value' in descriptor) ||
|
|
17
|
+
typeof descriptor.value !== 'string') {
|
|
18
|
+
throw new TypeError('Catalog entries must be enumerable string data properties.');
|
|
19
|
+
}
|
|
20
|
+
if (!key || key.length > 256 || descriptor.value.length > 10_000) {
|
|
21
|
+
throw new RangeError('Catalog keys are limited to 1..256 characters and values to 10000.');
|
|
22
|
+
}
|
|
23
|
+
entries.set(key, descriptor.value);
|
|
24
|
+
}
|
|
25
|
+
return entries;
|
|
26
|
+
}
|
|
27
|
+
function placeholders(text) {
|
|
28
|
+
return [...text.matchAll(/\{([A-Za-z][A-Za-z0-9_.-]{0,127})\}/g)]
|
|
29
|
+
.map((match) => match[1])
|
|
30
|
+
.sort();
|
|
31
|
+
}
|
|
32
|
+
function sameStrings(first, second) {
|
|
33
|
+
return first.length === second.length && first.every((value, index) => value === second[index]);
|
|
34
|
+
}
|
|
35
|
+
export function diffTranslationCatalogs(source, target) {
|
|
36
|
+
const sourceEntries = catalogEntries(source);
|
|
37
|
+
const targetEntries = catalogEntries(target);
|
|
38
|
+
const added = [...targetEntries.keys()].filter((key) => !sourceEntries.has(key)).sort();
|
|
39
|
+
const missing = [...sourceEntries.keys()].filter((key) => !targetEntries.has(key)).sort();
|
|
40
|
+
const shared = [...sourceEntries.keys()].filter((key) => targetEntries.has(key)).sort();
|
|
41
|
+
const changed = shared.filter((key) => sourceEntries.get(key) !== targetEntries.get(key));
|
|
42
|
+
const placeholderMismatches = shared.filter((key) => !sameStrings(placeholders(sourceEntries.get(key)), placeholders(targetEntries.get(key))));
|
|
43
|
+
return Object.freeze({
|
|
44
|
+
added: Object.freeze(added),
|
|
45
|
+
missing: Object.freeze(missing),
|
|
46
|
+
changed: Object.freeze(changed),
|
|
47
|
+
placeholderMismatches: Object.freeze(placeholderMismatches),
|
|
48
|
+
compatible: placeholderMismatches.length === 0,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export function createTranslationReview(input) {
|
|
52
|
+
if (input === null ||
|
|
53
|
+
typeof input !== 'object' ||
|
|
54
|
+
Array.isArray(input) ||
|
|
55
|
+
(Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null)) {
|
|
56
|
+
throw new TypeError('Review metadata must be a plain object.');
|
|
57
|
+
}
|
|
58
|
+
const allowed = ['locale', 'key', 'reviewer', 'status', 'reviewedAt', 'note'];
|
|
59
|
+
const values = {};
|
|
60
|
+
for (const property of Reflect.ownKeys(input)) {
|
|
61
|
+
const descriptor = typeof property === 'string' ? Object.getOwnPropertyDescriptor(input, property) : undefined;
|
|
62
|
+
if (typeof property !== 'string' ||
|
|
63
|
+
!allowed.includes(property) ||
|
|
64
|
+
!descriptor?.enumerable ||
|
|
65
|
+
!('value' in descriptor)) {
|
|
66
|
+
throw new TypeError('Review metadata contains an unsupported or non-data property.');
|
|
67
|
+
}
|
|
68
|
+
values[property] = descriptor.value;
|
|
69
|
+
}
|
|
70
|
+
for (const key of ['locale', 'key', 'reviewer', 'status', 'reviewedAt']) {
|
|
71
|
+
if (!Object.hasOwn(values, key))
|
|
72
|
+
throw new TypeError(`Review field "${key}" is required.`);
|
|
73
|
+
}
|
|
74
|
+
for (const key of ['locale', 'key', 'reviewer']) {
|
|
75
|
+
const value = values[key];
|
|
76
|
+
if (typeof value !== 'string' || value.length < 1 || value.length > 256) {
|
|
77
|
+
throw new RangeError(`${key} must contain 1..256 characters.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!['draft', 'reviewed', 'approved', 'rejected'].includes(values.status)) {
|
|
81
|
+
throw new TypeError('status must be draft, reviewed, approved, or rejected.');
|
|
82
|
+
}
|
|
83
|
+
const timestamp = typeof values.reviewedAt === 'string' &&
|
|
84
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/.test(values.reviewedAt)
|
|
85
|
+
? Date.parse(values.reviewedAt)
|
|
86
|
+
: Number.NaN;
|
|
87
|
+
if (!Number.isFinite(timestamp) ||
|
|
88
|
+
new Date(timestamp).toISOString().slice(0, 19) !== values.reviewedAt.slice(0, 19)) {
|
|
89
|
+
throw new TypeError('reviewedAt must be an ISO timestamp ending in Z.');
|
|
90
|
+
}
|
|
91
|
+
if (values.note !== undefined && (typeof values.note !== 'string' || values.note.length > 2000)) {
|
|
92
|
+
throw new RangeError('note must be a string of at most 2000 characters.');
|
|
93
|
+
}
|
|
94
|
+
return Object.freeze({
|
|
95
|
+
locale: values.locale,
|
|
96
|
+
key: values.key,
|
|
97
|
+
reviewer: values.reviewer,
|
|
98
|
+
status: values.status,
|
|
99
|
+
reviewedAt: values.reviewedAt,
|
|
100
|
+
...(values.note === undefined ? {} : { note: values.note }),
|
|
101
|
+
});
|
|
102
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lp-labs/translation-workflow",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Bounded translation catalog diffs, placeholder checks, and review metadata.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/cjs/index.js",
|
|
8
|
+
"module": "./dist/esm/index.js",
|
|
9
|
+
"types": "./dist/esm/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/esm/index.d.ts",
|
|
14
|
+
"default": "./dist/esm/index.js"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/cjs/index.d.ts",
|
|
18
|
+
"default": "./dist/cjs/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": "\u003e=18"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "node scripts/build.mjs",
|
|
34
|
+
"typecheck": "npm run build \u0026\u0026 node scripts/check-types.mjs",
|
|
35
|
+
"test": "npm run build \u0026\u0026 node --test tests/*.test.mjs",
|
|
36
|
+
"format": "prettier --single-quote --write src scripts tests *.json README.md",
|
|
37
|
+
"format:check": "prettier --single-quote --check src scripts tests *.json README.md",
|
|
38
|
+
"prepack": "npm run build"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"typescript": "^5.9.3",
|
|
42
|
+
"prettier": "^3.6.2"
|
|
43
|
+
},
|
|
44
|
+
"prettier": {
|
|
45
|
+
"singleQuote": true,
|
|
46
|
+
"semi": true,
|
|
47
|
+
"trailingComma": "all",
|
|
48
|
+
"printWidth": 100,
|
|
49
|
+
"endOfLine": "lf"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public",
|
|
53
|
+
"registry": "https://registry.npmjs.org/"
|
|
54
|
+
},
|
|
55
|
+
"author": {
|
|
56
|
+
"name": "Libin Prasanth",
|
|
57
|
+
"url": "https://www.linkedin.com/in/libinprasanth/"
|
|
58
|
+
},
|
|
59
|
+
"funding": {
|
|
60
|
+
"type": "paypal",
|
|
61
|
+
"url": "https://www.paypal.me/LibinPrasanth"
|
|
62
|
+
}
|
|
63
|
+
}
|