Skip to content

arnaudambro/i18n-keyless

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

92 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

i18n-keyless - Ultimate DX for i18n implementation. No key, use your natural language.

Welcome to i18n-keyless! 🚀 This package provides a seamless way to handle translations without the need for cumbersome key management. This README will guide you through the setup and usage of the library.

Try it by yourself in this Stackblitz


📜 Table of Contents


😎 How it works

First, you should read the What pains does it solve? section to understand the pains you have with the current i18n solutions.

i18n-keyless is a library, combined with an API service (I provide one, but you can use your own) that allows you to translate your text without the need to use keys.

By calling I18nKeyless.init you initialize an object that will be used to translate your text. If your primary language is en and the user's language is fr, the object would look like this:

{
   "Hello!": "Bonjour !",
   "Welcome to our website": "Bienvenue sur notre site web",
   ...
}

If the user's language is en, i18n-keyless won't use such an object and will use the default translations.

If the translation is not found, there will be an asynchronous fetch to I18nKeyless' API (or your own if you prefer) to get the translation by an AI API call. Then the translation is returned and stored in the object. This operation is only made once ever per key, for all the users all over the world. The operation can be made in dev mode if you encounter that key, but it can also be made in production if the key is dynamic.

At the first opening of the app ever in a new language, there is an API call to the server where all your translations are stored. Then it stores all those translations in the object and the storage you provide (localStorage, AsyncStorage, MMKV, etc.). No translations are stored in the app initially.

At each opening of the app, the newest translations are fetched from the storage and the object is updated.

🔧 Installation

React Installation

Install the package via npm or yarn:

npm install i18n-keyless-react

Node Installation

Install the package via npm or yarn:

npm install i18n-keyless-node

Quick Start

Get up and running in minutes!

React Quick Start

  1. Install:

    npm install i18n-keyless-react
  2. Initialize: Call init once at the root of your app (e.g., App.js or index.js).

    import { init } from "i18n-keyless-react";
    import myStorage from "./src/services/storage"; // Use your preferred storage solution
    
    init({
      API_KEY: "<YOUR_API_KEY>", // Get your key from i18n-keyless.com
      /**
         * the storage to use for the translations -  any storage that has a getItem, setItem, removeItem, or get, set, and remove method
        *
        * in React Native you can use react-native-mmkv, @react-native-async-storage/async-storage, and in Web window.localStorage, or idb-keyval for IndexedDB, or any storage whose methods are compatible
      */
      storage: myStorage, 
      languages: {
        primary: "en", // Your app's primary language
        supported: ["en", "fr", "es"], // Languages your app supports
      },
    });

    Note: You'll need an API_KEY from i18n-keyless.com or configure your own API.

  3. Use: Wrap text with the I18nKeylessText component.

    import { I18nKeylessText } from "i18n-keyless-react"; // `import { T } from "i18n-keyless-react"` also works
    import { setCurrentLanguage } from "i18n-keyless-react"; // Optional: for changing language
    
    // Example Component
    function MyComponent() {
      return (
        <div>
          <button onClick={() => setCurrentLanguage("fr")}>Set FR</button>
          <button onClick={() => setCurrentLanguage("es")}>Set ES</button>
          <h1>
            <I18nKeylessText>Welcome to our app!</I18nKeylessText>
          </h1>
          <p>
            <I18nKeylessText>This text will be automatically translated.</I18nKeylessText>
          </p>
          {/* Example with context for disambiguation */}
          <button>
            <I18nKeylessText context="this is a back button">Back</I18nKeylessText>
          </button>
        </div>
      );
    }

Node Quick Start

  1. Install:

    npm install i18n-keyless-node
  2. Initialize: Call init at the start of your application.

    import { init } from "i18n-keyless-node";
    
    (async () => {
      await init({
        API_KEY: "<YOUR_API_KEY>", // Get your key from i18n-keyless.com
        languages: {
          primary: "en", // Your primary language
          supported: ["en", "fr", "es"], // Languages you need translations for
        },
      });
      console.log("i18n-keyless initialized!");
    })();

    Note: You'll need an API_KEY from i18n-keyless.com or configure your own API.

  3. Use: Use awaitForTranslation to fetch and retrieve translations.

    import { awaitForTranslation } from "i18n-keyless-node";
    
    // Assuming init has completed
    (async () => {
      // Fetch and get the French translation for "Hello world"
      const greeting = await awaitForTranslation("Hello world", "fr"); // Target language 'fr'
      console.log(greeting); // Output: "Bonjour le monde" (or similar)
    
      // Fetch and get the Spanish translation for "Processing complete."
      const message = await awaitForTranslation("Processing complete.", "es"); // Target language 'es'
      console.log(message); // Output: "Procesamiento completo." (or similar)
    
      // Example with context
      const backButtonText = await awaitForTranslation("Back", "es", { context: "this is a back button" });
      console.log(backButtonText); // Output: Spanish translation for "Back" (e.g., "Atrás")
    
      // ⚠️ IMPORTANT: Always await translations to avoid API rate limiting
      // Bad - could get rate limited:
      awaitForTranslation("Hello", "fr");
      awaitForTranslation("World", "fr");
      
      // Good - await each translation:
      await awaitForTranslation("Hello", "fr");
      await awaitForTranslation("World", "fr");
      
      // Even better - await in parallel if possible:
      await Promise.all([
        awaitForTranslation("Hello", "fr"),
        awaitForTranslation("World", "fr") 
      ]);
    
    })();

🚀 React Usage (i18n-keyless-react)

Component Usage

Use the I18nKeylessText component to wrap your text in any supported language:

import { I18nKeylessText } from "i18n-keyless-react";

<I18nKeylessText>Je mets mon texte dans ma langue, finies les clés !</I18nKeylessText>

Dynamic Text Replacement

For text with dynamic content, use the I18nKeylessText component:

import { I18nKeylessText } from "i18n-keyless-react";

// Replace specific text patterns with dynamic values
<I18nKeylessText 
  replace={{
    "{name}": user.name,
    "{date}": formattedDate
  }}
>
  Bonjour {name}, votre rendez-vous est confirmé pour le {date}
</I18nKeylessText>

// This will first translate the entire text, then replace the placeholders with their respective values. It's perfect for dynamic content like usernames, dates, or counts.

Plural and gender

We didn't build any complicated internal system for plural and gender management. For now, you need to use JavaScript to switch between cases, and maybe context to specify plural and gender.

React Hooks and Methods

For translating text outside of components, use the getTranslation method:

import { getTranslation } from "i18n-keyless-react";

export default function Home() {
  return (
    <HomeTabs.Navigator>
      <HomeTabs.Screen
        options={{ tabBarLabel: getTranslation("Welcome") }}
        name="WELCOME"
      />
    </HomeTabs.Navigator>
  );
}

For setting a new current language, use the setCurrentLanguage method wherever you want:

import { setCurrentLanguage } from "i18n-keyless-react";

setCurrentLanguage("en");

To retrieve the current language, use the useCurrentLanguage hook:

import { useCurrentLanguage } from "i18n-keyless-react";

const currentLanguage = useCurrentLanguage();

Storage Management

Clear the i18n-keyless storage:

import { clearI18nKeylessStorage } from "i18n-keyless-react";

// Clear all translations from storage
clearI18nKeylessStorage();

🚀 Node Usage (i18n-keyless-node)

Initialization

Initialize the i18n system with your configuration (usually done once at startup):

import { init } from "i18n-keyless-node";

await init({
  API_KEY: "<YOUR_API_KEY>",
  languages: {
    primary: "fr",
    supported: ["fr", "en"]
  }
});

Translation Methods

awaitForTranslation (Asynchronous - MANDATORY AWAIT)

Use awaitForTranslation to retrieve a translation, automatically fetching it from the backend via API or custom handler if it's missing locally.

🚨 CRITICAL NODE.JS USAGE NOTE 🚨

You MUST await the awaitForTranslation function calls. You would be blocked by 429 Too many requests if you didn't.

Failure to do so is not optional. If the underlying translation process encounters an error (network issue, API error, etc.) and the promise rejects.

import { awaitForTranslation } from "i18n-keyless-node";

// --- CORRECT USAGE (Mandatory) ---
async function getGreetingSafe(name: string, lang: string): Promise<string> {
  const greetingTemplate = await awaitForTranslation("Hello {user}", lang);
  return greetingTemplate.replace("{user}", name);
}


// --- INCORRECT USAGE (Will crash on error) ---
awaitForTranslation("Processing complete.", "es")
  .then(message => {
    console.log(message); // Output: "Procesamiento completo."
  })
  .catch(error => {
    console.error("FATAL: Failed to get processing message:", error);
    // Handle error, maybe use fallback text
  });

// DO NOT DO THIS:
awaitForTranslation("This will crash if it rejects!", "de");

// ALSO DO NOT DO THIS (assigning promise without handling rejection):
const promise = awaitForTranslation("This also crashes if it rejects!", "it");

Managing Translations

Fetch all translations for all supported languages:

import { getAllTranslationsForAllLanguages } from "i18n-keyless-node";

// Fetch and update translation store with latest translations
const response = await getAllTranslationsForAllLanguages(store);
if (response?.ok) {
  // Handle successful translation update
  console.log("Translations updated successfully");
}

⚙️ Setup Options

While the Quick Start uses the i18n-keyless service via API_KEY, you have other options:

Using the i18n-keyless Service (Default)

This is the easiest way to get started. Provide your API_KEY during initialization as shown in the Quick Start guides.

(React Setup Example - Covered in Quick Start)

(Node Setup Example - Covered in Quick Start)

Using your own API

If you prefer to host your own translation backend, you can configure i18n-keyless to point to your API endpoints.

Using API_URL

To use your own API, you need to provide the API_URL in the init configuration. Your API must implement the following routes:

  • GET /translate/:lang: This route should return all translations for a given language. Response format to GET /translate/en:

    {
        "ok": true,
        "data": {
            "translations": {
                "Bonjour le monde": "Hello world",
                "Bienvenue chez nous": "Welcome to our website",
                "Au revoir": "Goodbye"    
            }
        },
        "error": null,
        "message": "" // there would be a message if the key is not valid, or whatever
    }
  • POST /translate: This route should accept a body with the key to translate and return the translated text. Request body:

    {
        "key": "Bonjour le monde",
        "languages": ["en","nl","it","de","es"],
        "primaryLanguage": "fr"
    }

    Response format:

    {
        "ok": true,
        "message": "", // there would be a message if the key is not valid, or whatever
        "data": { "translation": { "fr": "Bonjour tout le monde", "en": "Hello world" } }
    }

Here's how to configure with your API_URL:

// For React
import { init } from "i18n-keyless-react";
import myStorage from "./src/services/storage";

init({
    API_URL: "https://your-api.com",
    storage: myStorage,
    languages: {
        primary: "fr",
        supported: ["en", "fr"],
    },
});

// For Node.js
import { init } from "i18n-keyless-node";

await init({
    API_URL: "https://your-api.com",
    languages: {
        primary: "fr",
        supported: ["en", "fr"],
    },
});

Using Custom Handlers

Alternatively, you can provide custom functions to handle the translation and retrieval of all translations:

// For React
import { init } from "i18n-keyless-react";
import myStorage from "./src/services/storage";

async function handleTranslate(key, languages, primaryLanguage) {
    // Your custom logic to translate the key
    return { ok: true, message: "" };
}

async function getAllTranslations(lang) {
    // Your custom logic to fetch all translations for a specific language
    return {
        ok: true,
        data: {
            translations: {
                "Bonjour le monde": "Hello world",
            }
        }
    };
}

init({
    storage: myStorage,
    languages: {
        primary: "fr",
        supported: ["en", "fr"],
    },
    handleTranslate: handleTranslate,
    getAllTranslations: getAllTranslations
});

// For Node.js
import { init } from "i18n-keyless-node";

async function handleTranslate(key, languages, primaryLanguage) {
    // Your custom logic to translate the key
    return { ok: true, message: "" };
}

async function getAllTranslationsForAllLanguages() {
    // Your custom logic to fetch translations for all languages
    return {
        ok: true,
        data: {
            translations: {
                en: {
                    "Bonjour le monde": "Hello world"
                },
                fr: {}
            }
        }
    };
}

await init({
    languages: {
        primary: "fr",
        supported: ["en", "fr"],
    },
    handleTranslate: handleTranslate,
    getAllTranslationsForAllLanguages: getAllTranslationsForAllLanguages
});

🛠️ Custom Component Example (React)

For better integration and consistency, wrap I18nKeylessText within your own custom text component:

import { StyleProp, Text, TextProps, TextStyle } from "react-native";
import { I18nKeylessText, type I18nKeylessTextProps } from "i18n-keyless-react";
import { colors } from "~/utils/colors";

interface MyTextProps {
  className?: string;
  style?: StyleProp<TextStyle>;
  color?: keyof typeof colors;
  textProps?: TextProps;
  skipTranslation?: boolean;
  children: I18nKeylessTextProps["children"];
  debug?: I18nKeylessTextProps["debug"];
  context?: I18nKeylessTextProps["context"];
  replace?: I18nKeylessTextProps["replace"];
  forceTemporary?: I18nKeylessTextProps["forceTemporary"];
}

export default function MyText({
  className,
  style = {},
  children,
  color = "app-white",
  textProps,
  skipTranslation = false,
  debug = false,
  context,
  replace,
  forceTemporary,
}: MyTextProps) {
  if (skipTranslation) {
    if (debug) {
      console.log("skipTranslation", children);
    }
    return (
      <Text
        className={["text-dark dark:text-white", className].join(" ")}
        style={[style, { color: color ? colors[color] : undefined }]}
        {...textProps}
      >
        {children}
      </Text>
    );
  }
  if (debug) {
    console.log("children translated", children);
  }
  return (
    <Text
      className={["text-dark dark:text-white", className].join(" ")}
      style={[style, { color: color ? colors[color] : undefined }]}
      {...textProps}
    >
      <I18nKeylessText
        context={context}
        replace={replace}
        forceTemporary={forceTemporary}
        debug={debug}
      >
        {children}
      </I18nKeylessText>
    </Text>
  );
}

🔧 What pains does it solve?

Multiple pains exist with the current i18n solutions.

Pain Point Traditional i18n i18n-keyless
Key Management Manual key creation & maintenance required No keys needed - use natural language directly
Translation Management Manual tracking of missing translations across languages Automatic translation handling via AI
Code Readability Read cryptic keys like "user.welcome.message" Read actual text like "Welcome to our app!"
Setup Time Hours of dev setup + ongoing maintenance Minutes to initialize
Cost ~$1600 for 1000 keys (dev time) $8/month for 1000 keys

i18n key system management

Today most of the systems use keys to translate the text:

{
   "en": {
      "hello": "Hello"
   },
   "fr": {
      "hello": "Bonjour"
   }
}

This is painful to generate. This is painful to maintain.

When you see a text in the app, and you want to update it, you need to find the corresponding key, update the text, and make sure to not forget to update the key if needed.

With i18n-keyless, you don't care about the i18n system at all.

Translation management

With the key system, you also need to manage the translations in the app. You need to not forget any. In all the languages you support. You need to check manually, or create a script to do it.

With i18n-keyless, you don't care about the i18n system at all.

Code reading

With the key system, when you read the code and the content, you have to read keys, not natural language. So you don't really know what you are reading. Sometimes you should make a fix because a sentence is not grammatically correct. But you don't know that because you read keys, not natural language.

With i18n-keyless, you read natural language. So you know exactly what you are reading. And you can make sure the sentence is grammatically correct, in real time.

Time saving

With basic i18n system on your own, you need at least to

  • setup the keys' system: at least 1 hour of senior dev time
  • back and forth for each new key: 1 minutes per key, x1000 keys = 1000 minutes = 16 hours

At 100$ per hour, that's 1600$ for 1000 keys.

With i18n-keyless.com, at 8$ a month for 1000 keys, you can afford 200 months of subscription.

You can setup your own system : it took me at least 1.5 day to make it strong enough, that would cost you at least 1200$ for

  • handling translation with AI
  • in several languages
  • storage in DB
  • retrieving translations from DB
  • only the latest ones to make the service fast and efficient
  • handling multiple languages
  • maintaining the service

📬 Contact

Need help or have questions? Reach out to:


© 2025 i18n-keyless

About

i18n-keyless: setup i18n for your MVP with no pain

Topics

Resources

License

Stars

Watchers

Forks