lingo.dev 0.87.14 → 0.88.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 CHANGED
@@ -1,13 +1,176 @@
1
- # Lingo.dev CLI
1
+ <p align="center">
2
+ <a href="https://lingo.dev">
3
+ <img src="https://raw.githubusercontent.com/lingodotdev/lingo.dev/main/content/banner.dark.png" width="100%" alt="Lingo.dev" />
4
+ </a>
5
+ </p>
2
6
 
3
- CLI for Lingo.dev.
7
+ <p align="center">
8
+ <strong>⚡️ AI-powered open-source CLI for web & mobile localization.</strong>
9
+ </p>
4
10
 
5
- ### Installation
11
+ <br />
12
+
13
+ <p align="center">
14
+ <a href="https://docs.lingo.dev">Docs</a> •
15
+ <a href="https://github.com/lingodotdev/lingo.dev/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22">Contribute</a> •
16
+ <a href="#-github-action">GitHub Action</a> •
17
+ <a href="#">Star the repo</a>
18
+ </p>
19
+
20
+ <p align="center">
21
+ <a href="https://github.com/lingodotdev/lingo.dev/actions/workflows/release.yml">
22
+ <img src="https://github.com/lingodotdev/lingo.dev/actions/workflows/release.yml/badge.svg" alt="Release" />
23
+ </a>
24
+ <a href="https://github.com/lingodotdev/lingo.dev/blob/main/LICENSE.md">
25
+ <img src="https://img.shields.io/github/license/lingodotdev/lingo.dev" alt="License" />
26
+ </a>
27
+ <a href="https://github.com/lingodotdev/lingo.dev/commits/main">
28
+ <img src="https://img.shields.io/github/last-commit/lingodotdev/lingo.dev" alt="Last Commit" />
29
+ </a>
30
+ </p>
31
+
32
+ <br />
33
+
34
+ Lingo.dev is a community-driven, open-source CLI for AI-powered web and mobile app localization.
35
+
36
+ Lingo.dev is designed to produce authentic translations instantly, eliminating manual work and management overhead. As a result, teams do accurate localization 100x faster, shipping features to more happy users worldwide. It can be used with your own LLM or with Lingo.dev-managed Localization Engine.
37
+
38
+ > **Little-known fact:** Lingo.dev began as a small project at a student hackathon back in 2023! Many iterations later, we got accepted into Y Combinator in 2024, and we're now hiring! Interested in building the next-gen localization tools? Send your CV to careers@lingo.dev! 🚀
39
+
40
+ ## 📑 In This Guide
41
+
42
+ - [Quickstart](#-quickstart) - Get started in minutes
43
+ - [Caching](#-caching-with-i18nlock) - Optimize translation updates
44
+ - [GitHub Action](#-github-action) - Automate localization in CI/CD
45
+ - [Features](#-supercharged-features) - What makes Lingo.dev powerful
46
+ - [Documentation](#-documentation) - Detailed guides and references
47
+ - [Contribute](#-contribute) - Join our community
48
+
49
+ ## 💫 Quickstart
50
+
51
+ Lingo.dev CLI is designed to work with both your own LLM, and Lingo.dev-managed Localization Engine built on top of latest SOTA (state-of-the-art) LLMs.
52
+
53
+ ### Using Your Own LLM (BYOK or Bring Your Own Key)
54
+
55
+ 1. Create an `i18n.json` configuration file:
56
+
57
+ ```json
58
+ {
59
+ "version": 1.5,
60
+ "provider": {
61
+ "id": "anthropic",
62
+ "model": "claude-3-7-sonnet-latest",
63
+ "prompt": "You're translating text from {source} to {target}."
64
+ },
65
+ "locale": {
66
+ "source": "en",
67
+ "targets": ["es", "fr", "de"]
68
+ }
69
+ }
70
+ ```
71
+
72
+ 2. Set your API key as an environment variable:
73
+
74
+ ```bash
75
+ export ANTHROPIC_API_KEY=your_anthropic_api_key
76
+ # or for OpenAI
77
+ export OPENAI_API_KEY=your_openai_api_key
78
+ ```
79
+
80
+ 3. Run the localization:
81
+
82
+ ```bash
83
+ npx lingo.dev@latest i18n
84
+ ```
85
+
86
+ ### Using Lingo.dev Cloud
87
+
88
+ Oftentimes, production-grade apps require features like translation memory, glossary support, and localization quality assurance. Also, sometimes, you want an expert to decide for you which LLM provider and model to use, and to update the model automatically when new ones are released. Lingo.dev is a managed Localization Engine that provides these features:
89
+
90
+ 1. Create an `i18n.json` configuration file (without provider node):
91
+
92
+ ```json
93
+ {
94
+ "version": 1.5,
95
+ "locale": {
96
+ "source": "en",
97
+ "targets": ["es", "fr", "de"]
98
+ }
99
+ }
100
+ ```
101
+
102
+ 2. Authenticate with Lingo.dev:
103
+
104
+ ```bash
105
+ npx lingo.dev@latest auth --login
106
+ ```
107
+
108
+ 3. Run localization:
6
109
 
7
110
  ```bash
8
- npm i lingo.dev
111
+ npx lingo.dev@latest i18n
9
112
  ```
10
113
 
11
- ### Documentation
114
+ ## 📖 Documentation
115
+
116
+ For detailed guides and API references, visit the [documentation](https://lingo.dev/go/docs).
117
+
118
+ ## 🔒 Caching with `i18n.lock`
119
+
120
+ Lingo.dev uses an `i18n.lock` file to track content checksums, ensuring only changed text gets translated. This improves:
121
+
122
+ - ⚡️ **Speed**: Skip already translated content
123
+ - 🔄 **Consistency**: Prevent unnecessary retranslations
124
+ - 💰 **Cost**: No billing for repeated translations
125
+
126
+ ## 🤖 GitHub Action
127
+
128
+ Lingo.dev offers a GitHub Action to automate localization in your CI/CD pipeline. Here's a basic setup:
129
+
130
+ ```yaml
131
+ - uses: lingodotdev/lingo.dev@main
132
+ with:
133
+ api-key: ${{ secrets.LINGODOTDEV_API_KEY }}
134
+ ```
135
+
136
+ This action runs `lingo.dev i18n` on every push, keeping your translations up-to-date automatically.
137
+
138
+ For pull request mode and other configuration options, visit our [GitHub Action documentation](https://docs.lingo.dev/ci-action/gha).
139
+
140
+ ## ⚡️ Lingo.dev's Superpowers
141
+
142
+ - 🔥 **Instant integration**: Works with your codebase in minutes
143
+ - 🔄 **CI/CD Automation**: Set it and forget it
144
+ - 🌍 **Global reach**: Ship to users everywhere
145
+ - 🧠 **AI-powered**: Uses latest language models for natural translations
146
+ - 📊 **Format-agnostic**: JSON, YAML, CSV, Markdown, Android, iOS, and many more
147
+ - 🔍 **Clean diffs**: Preserves your file structure exactly
148
+ - ⚡️ **Lightning-fast**: Translations in seconds, not days
149
+ - 🔄 **Always synced**: Automatically updates when content changes
150
+ - 🌟 **Human quality**: Translations that don't sound robotic
151
+ - 👨‍💻 **Built by devs, for devs**: We use it ourselves daily
152
+ - 📈 **Grows with you**: From side project to enterprise scale
153
+
154
+ ## 🤝 Contribute
155
+
156
+ Lingo.dev is community-driven, so we welcome all contributions!
157
+
158
+ Have an idea for a new feature? Create a GitHub issue!
159
+
160
+ Want to contribute? Create a pull request!
161
+
162
+ ## 🌐 Readme in other languages
163
+
164
+ - [English](https://github.com/lingodotdev/lingo.dev)
165
+ - [Spanish](/readme/es.md)
166
+ - [French](/readme/fr.md)
167
+ - [Russian](/readme/ru.md)
168
+ - [German](/readme/de.md)
169
+ - [Chinese](/readme/zh-Hans.md)
170
+ - [Korean](/readme/ko.md)
171
+ - [Japanese](/readme/ja.md)
172
+ - [Italian](/readme/it.md)
173
+ - [Arabic](/readme/ar.md)
174
+ - [Hindi](/readme/hi.md)
12
175
 
13
- [Documentation](https://lingo.dev/go/docs)
176
+ Don't see your language? Just add a new language code to the [`i18n.json`](./i18n.json) file and open a PR.
package/build/cli.cjs CHANGED
@@ -865,6 +865,9 @@ function getBuckets(i18nConfig) {
865
865
  if (bucketEntry.lockedKeys) {
866
866
  config.lockedKeys = bucketEntry.lockedKeys;
867
867
  }
868
+ if (bucketEntry.lockedPatterns) {
869
+ config.lockedPatterns = bucketEntry.lockedPatterns;
870
+ }
868
871
  return config;
869
872
  });
870
873
  return result;
@@ -3139,7 +3142,7 @@ function md5(input2) {
3139
3142
 
3140
3143
  var fenceRegex = /([ \t]*)(^>\s*)?```([\s\S]*?)```/gm;
3141
3144
  var inlineCodeRegex = /(?<!`)`([^`\r\n]+?)`(?!`)/g;
3142
- var imageRegex = /([ \t]*)(^>\s*)?!\[[^\]]*?\]\([^\n\r]*?\)/gm;
3145
+ var imageRegex = /([ \t]*)(^>\s*)?!\[[^\]]*?\]\(([^()]*(\([^()]*\)[^()]*)*)\)/gm;
3143
3146
  function ensureSurroundingImageNewlines(_content) {
3144
3147
  let found = false;
3145
3148
  let content = _content;
@@ -3278,8 +3281,57 @@ function createMdxSectionsSplit2Loader() {
3278
3281
  });
3279
3282
  }
3280
3283
 
3284
+ // src/cli/loaders/mdx2/locked-patterns.ts
3285
+ function extractLockedPatterns(content, patterns = []) {
3286
+ let finalContent = content;
3287
+ const lockedPlaceholders = {};
3288
+ if (!patterns || patterns.length === 0) {
3289
+ return { content: finalContent, lockedPlaceholders };
3290
+ }
3291
+ for (const patternStr of patterns) {
3292
+ try {
3293
+ const pattern = new RegExp(patternStr, "gm");
3294
+ const matches = Array.from(finalContent.matchAll(pattern));
3295
+ for (const match of matches) {
3296
+ const matchedText = match[0];
3297
+ const matchHash = md5(matchedText);
3298
+ const placeholder = `---LOCKED-PATTERN-${matchHash}---`;
3299
+ lockedPlaceholders[placeholder] = matchedText;
3300
+ finalContent = finalContent.replace(matchedText, placeholder);
3301
+ }
3302
+ } catch (error) {
3303
+ console.warn(`Invalid regex pattern: ${patternStr}`);
3304
+ }
3305
+ }
3306
+ return {
3307
+ content: finalContent,
3308
+ lockedPlaceholders
3309
+ };
3310
+ }
3311
+ function createMdxLockedPatternsLoader(defaultPatterns) {
3312
+ return createLoader({
3313
+ async pull(locale, input2, initCtx, originalLocale) {
3314
+ const patterns = defaultPatterns || [];
3315
+ const { content } = extractLockedPatterns(input2 || "", patterns);
3316
+ return content;
3317
+ },
3318
+ async push(locale, data, originalInput, originalLocale, pullInput, pullOutput) {
3319
+ const patterns = defaultPatterns || [];
3320
+ if (!pullInput) {
3321
+ return data;
3322
+ }
3323
+ const { lockedPlaceholders } = extractLockedPatterns(pullInput, patterns);
3324
+ let result = data;
3325
+ for (const [placeholder, original] of Object.entries(lockedPlaceholders)) {
3326
+ result = result.replaceAll(placeholder, original);
3327
+ }
3328
+ return result;
3329
+ }
3330
+ });
3331
+ }
3332
+
3281
3333
  // src/cli/loaders/index.ts
3282
- function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys) {
3334
+ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys, lockedPatterns) {
3283
3335
  switch (bucketType) {
3284
3336
  default:
3285
3337
  throw new Error(`Unsupported bucket type: ${bucketType}`);
@@ -3349,6 +3401,7 @@ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys)
3349
3401
  bucketPathPattern
3350
3402
  }),
3351
3403
  createMdxCodePlaceholderLoader(),
3404
+ createMdxLockedPatternsLoader(lockedPatterns),
3352
3405
  createMdxFrontmatterSplitLoader(),
3353
3406
  createMdxSectionsSplit2Loader(),
3354
3407
  createLocalizableMdxDocumentLoader(),
@@ -3673,13 +3726,14 @@ function withExponentialBackoff(fn, maxAttempts = 3, baseDelay = 1e3) {
3673
3726
  }
3674
3727
 
3675
3728
  // src/cli/utils/observability.ts
3676
- var _posthognode = require('posthog-node');
3729
+ var _posthognode = require('posthog-node'); var _posthognode2 = _interopRequireDefault(_posthognode);
3730
+ var { PostHog } = _posthognode2.default;
3677
3731
  async function trackEvent(distinctId, event, properties) {
3678
3732
  if (process.env.DO_NOT_TRACK) {
3679
3733
  return;
3680
3734
  }
3681
3735
  try {
3682
- const posthog = new (0, _posthognode.PostHog)("phc_eR0iSoQufBxNY36k0f0T15UvHJdTfHlh8rJcxsfhfXk", {
3736
+ const posthog = new PostHog("phc_eR0iSoQufBxNY36k0f0T15UvHJdTfHlh8rJcxsfhfXk", {
3683
3737
  host: "https://eu.i.posthog.com",
3684
3738
  flushAt: 1,
3685
3739
  flushInterval: 0
@@ -3927,7 +3981,8 @@ var i18n_default = new (0, _interactivecommander.Command)().command("i18n").desc
3927
3981
  defaultLocale: sourceLocale,
3928
3982
  injectLocale: bucket.injectLocale
3929
3983
  },
3930
- bucket.lockedKeys
3984
+ bucket.lockedKeys,
3985
+ bucket.lockedPatterns
3931
3986
  );
3932
3987
  bucketLoader.setDefaultLocale(sourceLocale);
3933
3988
  await bucketLoader.init();
@@ -4113,7 +4168,8 @@ var i18n_default = new (0, _interactivecommander.Command)().command("i18n").desc
4113
4168
  defaultLocale: sourceLocale,
4114
4169
  injectLocale: bucket.injectLocale
4115
4170
  },
4116
- bucket.lockedKeys
4171
+ bucket.lockedKeys,
4172
+ bucket.lockedPatterns
4117
4173
  );
4118
4174
  bucketLoader.setDefaultLocale(sourceLocale);
4119
4175
  await bucketLoader.init();
@@ -5724,7 +5780,7 @@ function validateParams2(i18nConfig, flags) {
5724
5780
  // package.json
5725
5781
  var package_default = {
5726
5782
  name: "lingo.dev",
5727
- version: "0.87.14",
5783
+ version: "0.88.0",
5728
5784
  description: "Lingo.dev CLI",
5729
5785
  private: false,
5730
5786
  publishConfig: {