npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@devcoda/lokal-core

v1.3.9

Published

Core business logic for LOKAL - AST parsing, AI connectors, and storage

Readme

LOKAL - AI-Powered Localization for React

CI npm version License: MIT

LOKAL is an AI-powered localization ecosystem that helps you extract, translate, and manage translation strings in your React applications.

Features

  • CLI Commands - Easy command-line workflow
  • AST-based Extraction - Automatically finds translation strings in your code
  • AI Translation - Powered by Google Gemini, OpenAI, or DeepSeek
  • Incremental Sync - Only translates changed content using content hashing
  • File Storage - JSON-based locale file management
  • Framework Ready - Works with React and React Native

Installation

# Install CLI globally
npm install -g lokal

# Or use with npx
npx lokal --help

CLI Commands

LOKAL provides three main CLI commands:

1. lokal init

Initialize LOKAL in your project:

npx lokal init

Options:

  • -l, --locales <locales> - Comma-separated locales (default: "en")
  • -d, --default-locale <locale> - Default locale (default: "en")
  • -f, --force - Force reinitialization

What it does:

  • Creates lokal.config.js
  • Creates locales/ directory
  • Creates default locale file (e.g., locales/en.json)

2. lokal scan

Scan source files for translation strings:

npx lokal scan

Options:

  • -c, --config <path> - Path to config file
  • -o, --output <path> - Output directory
  • -v, --verbose - Show detailed output

What it does:

  • Scans ./src for t() and <T> patterns
  • Extracts all translation strings
  • Updates the default locale file

3. lokal translate

Translate missing strings using AI:

# Translate to first non-default locale
npx lokal translate

# Translate to specific locale
npx lokal translate --locale es

# Translate all locales
npx lokal translate --all

Options:

  • -c, --config <path> - Path to config file
  • -l, --locale <locale> - Specific locale to translate
  • -a, --all - Translate all locales
  • -v, --verbose - Show detailed output

Configuration

After running lokal init, a lokal.config.js file is created:

module.exports = {
  // Supported locales
  locales: ['en', 'es', 'fr'],
  
  // Default locale
  defaultLocale: 'en',
  
  // Function name for translations (t("key"))
  functionName: 't',
  
  // Component name for translations (<T>key</T>)
  componentName: 'T',
  
  // Source directory to scan
  sourceDir: './src',
  
  // Output directory for locale files
  outputDir: './locales',
  
  // AI Translation settings
  ai: {
    provider: 'gemini',  // 'openai', 'gemini', or 'deepseek'
    apiKey: process.env.GEMINI_API_KEY,
    model: 'gemini-2.5-flash'
  }
};

Complete Workflow

# Step 1: Initialize your project
npx lokal init --locales en,es,fr --default-locale en

# Step 2: Add translation strings to your code
# t("Hello") or <T>Hello</T>

# Step 3: Scan for strings (run after adding new strings)
npx lokal scan

# Step 4: Translate with AI
npx lokal translate --all

Library Usage (Advanced)

You can also use LOKAL as a library in your code:

import { ASTParser, GeminiProvider, AITranslator, FileStorage } from 'lokal-core';

Architecture

LOKAL consists of three main components:

┌─────────────────────────────────────────────┐
│              LOKAL Core                      │
├─────────────────────────────────────────────┤
│                                             │
│  ┌─────────────┐  ┌─────────────┐           │
│  │ AST Parser  │  │ AI Translate│           │
│  └──────┬──────┘  └──────┬──────┘           │
│         │                │                   │
│         ▼                ▼                   │
│  ┌─────────────────────────────┐            │
│  │    Translation Workflow     │            │
│  │  1. Extract strings        │            │
│  │  2. Translate with AI      │            │
│  │  3. Save to files          │            │
│  └──────────────┬──────────────┘            │
│                 ▼                           │
│  ┌─────────────────────────────┐            │
│  │     File Storage            │            │
│  └─────────────────────────────┘            │
│                                             │
└─────────────────────────────────────────────┘

1. AST Parser

The AST Parser scans your source code and extracts translation strings using Abstract Syntax Tree parsing.

Supported Patterns

| Pattern | Code Example | Extracts | |---------|--------------|----------| | Function call | t("Hello World") | "Hello World" | | Function with variable | t(greeting) | ❌ (not extracted) | | JSX Component | <T>Welcome</T> | "Welcome" | | JSX with expression | <T>{name}</T> | ❌ (not extracted) |

Basic Usage

import { ASTParser } from 'lokal-core';

// Create parser with default settings
const parser = new ASTParser({
    filePath: './src'  // Path to scan
});

// Parse a single file
const result = parser.parseFile('./src/components/Button.tsx');

console.log(result.strings);
// [
//   { key: "Submit", value: "Submit", file: "...", line: 10, column: 5 },
//   { key: "Cancel", value: "Cancel", file: "...", line: 11, column: 5 }
// ]

Custom Function and Component Names

By default, LOKAL looks for t() function and <T> component. You can customize this:

const parser = new ASTParser({
    filePath: './src',
    functionName: 'translate',  // Looks for translate("string")
    componentName: 'Trans'      // Looks for <Trans>string</Trans>
});

Scan Directory

// Scan entire directory
const result = parser.scanDirectory('./src', ['.ts', '.tsx', '.js', '.jsx']);

// result.strings - Array of extracted strings
// result.errors  - Any parsing errors

Type Definitions

interface ExtractedString {
    key: string;      // Translation key (same as value for simple strings)
    value: string;    // The actual string content
    file: string;     // File path where found
    line: number;     // Line number
    column: number;   // Column number
}

interface ScanResult {
    strings: ExtractedString[];
    errors: string[];
}

2. AI Translation

LOKAL supports multiple AI providers for translating your strings.

Supported Providers

| Provider | Model | API Key | |----------|-------|---------| | Google Gemini | gemini-2.5-flash | Google AI Studio | | OpenAI | gpt-4o-mini | OpenAI Platform | | DeepSeek | deepseek-chat | DeepSeek Platform |

Google Gemini (Recommended)

import { GeminiProvider, AITranslator } from 'lokal-core';

// Create Gemini provider
const gemini = new GeminiProvider(
    'YOUR-GEMINI-API-KEY',  // Get from https://aistudio.google.com/app/apikey
    'gemini-2.5-flash'       // Model name (optional)
);

// Create translator
const translator = new AITranslator(gemini);

OpenAI

import { OpenAIProvider, AITranslator } from 'lokal-core';

const openai = new OpenAIProvider(
    'YOUR-OPENAI-API-KEY',  // Get from https://platform.openai.com/api-keys
    'gpt-4o-mini'           // Model (optional)
);

const translator = new AITranslator(openai);

DeepSeek

import { DeepSeekProvider, AITranslator } from 'lokal-core';

const deepseek = new DeepSeekProvider(
    'YOUR-DEEPSEEK-API-KEY',
    'deepseek-chat'
);

const translator = new AITranslator(deepseek);

Translate Strings

// Single translation
const result = await translator.translate({
    sourceText: 'Hello World',
    sourceLocale: 'en',
    targetLocale: 'es'
});

console.log(result.translatedText); // "Hola Mundo"

// Batch translation
const results = await translator.translateBatch([
    { sourceText: 'Hello', sourceLocale: 'en', targetLocale: 'es' },
    { sourceText: 'Goodbye', sourceLocale: 'en', targetLocale: 'es' }
]);

Incremental Sync

LOKAL uses content hashing to skip translating unchanged strings:

// First translation
const sourceData = { greeting: 'Hello', welcome: 'Welcome' };
const targetData = {};

const translated1 = await translator.translateMissingKeys(
    sourceData,
    targetData,
    'en',
    'es'
);
// translated1 = { greeting: 'Hola', welcome: 'Bienvenido' }

// Second call with SAME source - skips already translated
const translated2 = await translator.translateMissingKeys(
    sourceData,    // Same as before
    translated1,  // Previous result
    'en',
    'es'
);
// Returns translated1 immediately (skips API calls)

// Third call with CHANGED source - only translates changed
sourceData.greeting = 'Hi';  // Changed!
const translated3 = await translator.translateMissingKeys(
    sourceData,
    translated2,
    'en',
    'es'
);
// Only 'greeting' is re-translated

3. File Storage

LOKAL provides file-based storage for your locale files.

Basic Usage

import { FileStorage } from 'lokal-core';

const storage = new FileStorage('./locales');

Save Locale

const localeData = {
    common: {
        greeting: 'Hello',
        goodbye: 'Goodbye'
    },
    home: {
        title: 'Welcome',
        subtitle: 'Welcome to our app'
    }
};

storage.saveLocale('en', localeData);
// Creates: locales/en.json

Load Locale

const enData = storage.loadLocale('en');
// Returns: { locale: 'en', data: {...}, hash: '...', lastUpdated: '...' }

// Get just the data
const data = enData?.data;

Merge Data

// Merge new keys into existing locale
const merged = storage.mergeLocaleData('en', newKeys, preserveExisting = true);

// Example:
const existing = { common: { greeting: 'Hello' } };
const newKeys = { common: { greeting: 'Hi', newKey: 'New' } };
const result = storage.mergeLocaleData('en', newKeys, true);
// result = { common: { greeting: 'Hello', newKey: 'New' } }

Other Operations

// Check if locale exists
storage.localeExists('en'); // true/false

// Get all available locales
storage.getAvailableLocales(); // ['en', 'es', 'fr']

// Delete locale
storage.deleteLocale('fr');

Complete Example: Translate a React App

Step 1: Create the translation script

// translate.ts
import { ASTParser, GeminiProvider, AITranslator, FileStorage } from 'lokal-core';

async function translateApp() {
    const SOURCE_DIR = './src';
    const TARGET_LOCALES = ['es', 'fr', 'de', 'ja'];
    const SOURCE_LOCALE = 'en';

    // 1. Extract strings from source
    console.log('🔍 Scanning for translation strings...');
    const parser = new ASTParser({ filePath: SOURCE_DIR });
    const scanResult = parser.scanDirectory(SOURCE_DIR);

    if (scanResult.errors.length > 0) {
        console.warn('⚠️  Some files had errors:', scanResult.errors);
    }

    // 2. Build source locale data
    const sourceData: Record<string, any> = {};
    for (const str of scanResult.strings) {
        sourceData[str.key] = str.value;
    }

    // 3. Save source locale
    console.log('💾 Saving source locale...');
    const storage = new FileStorage('./locales');
    storage.saveLocale(SOURCE_LOCALE, sourceData);

    // 4. Translate to each target locale
    console.log('🤖 Starting AI translation...');
    const provider = new GeminiProvider('YOUR-API-KEY');
    const translator = new AITranslator(provider);

    for (const targetLocale of TARGET_LOCALES) {
        console.log(`   Translating to ${targetLocale}...`);
        
        const targetData = storage.loadLocale(targetLocale)?.data || {};
        const translated = await translator.translateMissingKeys(
            sourceData,
            targetData,
            SOURCE_LOCALE,
            targetLocale
        );

        storage.saveLocale(targetLocale, translated);
        console.log(`   ✓ ${targetLocale} complete`);
    }

    console.log('✅ Translation complete!');
}

translateApp().catch(console.error);

Step 2: Run the script

npx tsx translate.ts

Step 3: Use in your React app

// locales/en.json
{
    "greeting": "Hello",
    "welcome": "Welcome to our app"
}

// locales/es.json
{
    "greeting": "Hola",
    "welcome": "Bienvenido a nuestra aplicación"
}

// App.jsx
import en from './locales/en.json';
import es from './locales/es.json';

const locales = { en, es };
const [locale, setLocale] = useState('en');

function t(key) {
    return locales[locale]?.[key] || key;
}

function App() {
    return (
        <div>
            <h1>{t('greeting')}</h1>
            <p>{t('welcome')}</p>
        </div>
    );
}

API Reference

ASTParser

new ASTParser(options: ScanOptions)

ScanOptions:

  • filePath: string - Path to file or directory
  • functionName?: string - Translation function name (default: 't')
  • componentName?: string - Translation component name (default: 'T')

Methods:

  • parseFile(filePath: string): ScanResult - Parse single file
  • parseContent(content: string, filePath?: string): ScanResult - Parse content string
  • scanDirectory(dirPath: string, extensions?: string[]): ScanResult - Scan directory

TranslationProvider

interface TranslationProvider {
    translate(request: TranslationRequest): Promise<TranslationResult>;
    translateBatch(requests: TranslationRequest[]): Promise<TranslationResult[]>;
}

TranslationRequest:

{
    sourceText: string;
    sourceLocale: string;
    targetLocale: string;
    context?: string;
}

TranslationResult:

{
    translatedText: string;
    sourceText: string;
    success: boolean;
    error?: string;
}

AITranslator

new AITranslator(provider: TranslationProvider)

Methods:

  • translateMissingKeys(sourceData, targetData, sourceLocale, targetLocale): Promise<LocaleData>

FileStorage

new FileStorage(basePath: string)

Methods:

  • loadLocale(locale: string): LocaleFile | null
  • saveLocale(locale: string, data: LocaleData): void
  • loadAllLocales(): Map<string, LocaleFile>
  • localeExists(locale: string): boolean
  • deleteLocale(locale: string): boolean
  • getAvailableLocales(): string[]
  • mergeLocaleData(locale: string, newData: LocaleData, preserveExisting?: boolean): LocaleData

Error Handling

All AI providers return proper error information:

const result = await translator.translate({
    sourceText: 'Hello',
    sourceLocale: 'en',
    targetLocale: 'es'
});

if (!result.success) {
    console.error('Translation failed:', result.error);
    // Handle errors appropriately
}

Common errors:

  • 429 - Rate limit exceeded
  • insufficient_quota - No API credits left
  • invalid_api_key - Invalid API key
  • model_not_found - Model doesn't exist

License

MIT