{"id":24600741,"url":"https://github.com/Oval-Tutu/smiti18n","last_synced_at":"2025-10-05T21:31:54.361Z","repository":{"id":272085803,"uuid":"915483447","full_name":"Oval-Tutu/smiti18n","owner":"Oval-Tutu","description":"A very complete internationalization library for Lua with LÖVE support 🌕💕","archived":false,"fork":false,"pushed_at":"2025-01-19T14:51:56.000Z","size":566,"stargazers_count":21,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-23T07:18:28.895Z","etag":null,"topics":["i18n","l10n","love2d","lua","luajit"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Oval-Tutu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"flexiondotorg"}},"created_at":"2025-01-12T00:35:07.000Z","updated_at":"2025-02-04T20:38:35.000Z","dependencies_parsed_at":"2025-01-19T12:34:57.644Z","dependency_job_id":null,"html_url":"https://github.com/Oval-Tutu/smiti18n","commit_stats":null,"previous_names":["oval-tutu/smiti18n"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/Oval-Tutu/smiti18n","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Oval-Tutu%2Fsmiti18n","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Oval-Tutu%2Fsmiti18n/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Oval-Tutu%2Fsmiti18n/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Oval-Tutu%2Fsmiti18n/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Oval-Tutu","download_url":"https://codeload.github.com/Oval-Tutu/smiti18n/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Oval-Tutu%2Fsmiti18n/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278525557,"owners_count":26001321,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-05T02:00:06.059Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["i18n","l10n","love2d","lua","luajit"],"created_at":"2025-01-24T14:01:26.121Z","updated_at":"2025-10-05T21:31:54.043Z","avatar_url":"https://github.com/Oval-Tutu.png","language":"Lua","funding_links":["https://github.com/sponsors/flexiondotorg"],"categories":["Recently Updated","Utilities"],"sub_categories":["[Jan 22, 2025](/content/2025/01/22/README.md)"],"readme":"# smiti18n\n\nA very complete internationalization library for Lua with LÖVE support 🌕💕\n\n[![Test 🧪](https://github.com/Oval-Tutu/smiti18n/actions/workflows/ci.yml/badge.svg)](https://github.com/Oval-Tutu/smiti18n/actions/workflows/ci.yml) [![Lua Versions](https://img.shields.io/badge/Lua-5.1%20%7C%205.2%20%7C%205.3%20%7C%205.4%20%7C%20JIT-24a6db)](https://github.com/Oval-Tutu/smiti18n) [![LÖVE Versions](https://img.shields.io/badge/L%C3%96VE-11.x-e14895)](https://love2d.org) [![LuaRocks](https://img.shields.io/luarocks/v/flexiondotorg/smiti18n?color=2b3c64)](https://luarocks.org/modules/flexiondotorg/smiti18n) [![License](https://img.shields.io/github/license/Oval-Tutu/smiti18n?color=a01830)](https://github.com/Oval-Tutu/smiti18n/blob/master/LICENSE)\n\n\n## Introduction\n\nsmiti18n (*pronounced smitten*) is a powerful internationalization (i18n) library that helps you create multilingual applications in Lua and [LÖVE](https://love2d.org/).\n\n**Core Features:** 🚀\n- 📁 Smart file-based loading \u0026 fallbacks\n- 🔠 Rich text interpolation \u0026 pluralization\n- 🌏 Locale-aware formatting for numbers, dates and currency\n- 💕 Built for LÖVE game engine\n\n**Rich Game Content:** 🎮\n- 💬 Complex dialogue support:\n  - Branching conversations\n  - Character-specific translations\n  - Context-aware responses\n- 🎯 53 locales, 650+ game-specific phrases\n- 📊 36 regional number formats\n\nAn intuitive API for managing translations forked from [i18n.lua](https://github.com/kikito/i18n.lua) by Enrique García Cota incorporating a [collection of community contributions](https://github.com/kikito/i18n.lua/pulls).\nThe number, date and time formatting has been ported from [Babel](https://github.com/LuaDist-testing/babel). Includes translations from [PolyglotGamedev](https://github.com/PolyglotGamedev). Significantly expanded test coverage and documentation.\n\n### Requirements\n\n- Lua 5.1-5.4 or LuaJIT 2.0-2.1\n- LÖVE 11.0+ (*optional*)\n\n### Quick example\n\nHere's a quick example:\n\n```lua\ni18n = require 'smiti18n'\n\n-- Load some translations\ni18n.load({\n  en = {\n    greeting = \"Hello %{name}!\",\n    messages = {\n      one = \"You have one new message\",\n      other = \"You have %{count} new messages\"\n    }\n  },\n  es = {\n    greeting = \"¡Hola %{name}!\",\n    messages = {\n      one = \"Tienes un mensaje nuevo\",\n      other = \"Tienes %{count} mensajes nuevos\"\n    }\n  }\n})\n\n-- Set the current locale\ni18n.setLocale('es')\n\n-- Use translations\nprint(i18n('greeting', {name = \"Luna\"}))     -- ¡Hola Luna!\nprint(i18n('messages', {count = 3}))         -- Tienes 3 mensajes nuevos\n```\n\n## Installation\n\n### Using LuaRocks\n\n[smiti18n is available on LuaRocks](https://luarocks.org/modules/flexiondotorg/smiti18n). You can install it with the following command:\n\n```shell\nluarocks install smiti18n\n```\n\n### Manual Installation\n\n#### Option 1: Git Clone\n\nClone this repository and copy the `smiti18n` folder into your project as something like `lib/smiti18n`.\n\n```shell\ngit clone https://github.com/Oval-Tutu/smiti18n.git\ncd smiti18n\ncp -r smiti18n your-project/lib/\n```\n\n#### Option 2: Download Release\n\n1. Download latest release from [Releases](https://github.com/Oval-Tutu/smiti18n/releases)\n2. Extract the archive\n3. Copy `smiti18n` directory to your project\n\nProject structure after installation:\n\n```\nyour-project/\n  ├── lib/\n  │   └── smiti18n/\n  │       ├── init.lua\n  │       ├── interpolate.lua\n  │       ├── plural.lua\n  │       ├── variants.lua\n  │       └── version.lua\n  ├── locales/\n  │   ├── en-UK.lua\n  │   └── other-locale-files.lua\n  └── main.lua\n```\n\n#### Using the Library\n\n```lua\n-- Require smiti18n in your code\nlocal i18n = require 'lib.smiti18n'\ni18n.loadFile('locales/en-UK.lua')\ni18n.setLocale('en-UK')\n```\n\n### Translation Files\n\nsmiti18n supports both single-file and multi-file approaches for managing translations.\n\n#### Single File\n\nStore all translations in one file (e.g., `translations.lua`):\n\n```lua\n-- translations.lua\nreturn {\n  en = {\n    greeting = \"Hello!\",\n    messages = {\n      one = \"You have one message\",\n      other = \"You have %{count} messages\"\n    }\n  },\n  es = {\n    greeting = \"¡Hola!\",\n    messages = {\n      one = \"Tienes un mensaje\",\n      other = \"Tienes %{count} mensajes\"\n    }\n  }\n}\n\n-- Load translations\ni18n.loadFile('translations.lua')\n```\n\n#### Multiple Files\n\nOrganize translations by language (recommended for larger projects):\n\nHere's an example project structure:\n\n```\nlocales/\n  ├── en.lua   -- English translations\n  ├── es.lua   -- Spanish translations\n  └── fr.lua   -- French translations\n```\n\n**`en.lua`**\n```lua\nreturn {\n  en = {  -- Locale key required\n    greeting = \"Hello!\",\n    messages = {\n      one = \"You have one message\",\n      other = \"You have %{count} messages\"\n    }\n  }\n}\n```\n\n```lua\n…\ni18n.loadFile('locales/en.lua') -- English translation\ni18n.loadFile('locales/es.lua') -- Spanish translation\ni18n.loadFile('locales/fr.lua') -- French translation\n…\n```\n\n#### Key Points\n- Files must include locale key in returned table\n- Can be loaded in any order\n- Later loads override earlier translations\n\n### Locales and Fallbacks\n\nsmiti18n provides flexible locale support with automatic fallbacks and regional variants.\n\n#### Locale Naming\n\n- Pattern: `language-REGION` (e.g., 'en-US', 'es-MX', 'pt-BR')\n- Separator: hyphen (-) only\n- Not supported: underscores, spaces, or other separators\n\n#### Fallback Chain\n\nsmiti18n implements a robust fallback system:\n\n1. **Current Locale** ('es-MX')\n2. **Parent Locales** (if defined, e.g., 'es-419')\n3. **Root Locale** ('es')\n4. **Default Value** (if provided)\n5. **nil** (if no matches found)\n\n```lua\n-- Example showing fallback chain\ni18n.load({\n  es = {\n    greeting = \"¡Hola!\",\n  },\n  [\"es-MX\"] = {\n    farewell = \"¡Adiós!\"\n  }\n})\n\ni18n.setLocale('es-MX')\nprint(i18n('farewell'))                -- \"¡Adiós!\"    (from es-MX)\nprint(i18n('greeting'))                -- \"¡Hola!\"     (from es)\nprint(i18n('missing'))                 -- nil          (not found)\nprint(i18n('missing', {\n    default = 'Not found'\n}))                                    -- \"Not found\"   (default value)\n```\n\n#### Multiple Locales\n\nFor handling regional variants, you can specify multiple locales in order of preference:\n\n```lua\ni18n.load({\n  ['es-419'] = { cookie = 'galleta' },    -- Latin American\n  ['es-ES']  = { cookie = 'galletita' },  -- European\n  ['es']     = { thanks = 'gracias' }     -- Generic\n})\n\n-- Set multiple locales in priority order\ni18n.setLocale({'es-419', 'es-ES', 'es'})\n\ni18n('cookie')  -- Returns 'galleta' (from es-419)\ni18n('thanks')  -- Returns 'gracias' (from es)\n```\n\nKey benefits of multiple locales:\n- Handle regional variations (e.g., pt-BR vs pt-PT)\n- Share base translations across regions\n- Create fallback chains (e.g., es-MX → es-419 → es)\n- Support partial translations with automatic fallback\n\n**💡NOTE!** Locales are tried in order of preference, with duplicates automatically removed.\n\n### String Interpolation\n\nsmiti18n supports three different styles of variable interpolation:\n\n#### Named Variables (*Recommended*)\n\nNamed variables are the recommended approach as they make translations more maintainable and less error-prone.\n\n```lua\ni18n.set('greeting', 'Hello %{name}, you are %{age} years old')\ni18n('greeting', {name = 'Alice', age = 25})  -- Hello Alice, you are 25 years old\n```\n#### Lua Format Specifiers\n```lua\ni18n.set('stats', 'Score: %d, Player: %s')\ni18n('stats', {1000, 'Bob'})  -- Score: 1000, Player: Bob\n```\n#### Advanced Formatting\n```lua\ni18n.set('profile', 'User: %\u003cname\u003e.q | Age: %\u003cage\u003e.d | Level: %\u003clevel\u003e.o')\ni18n('profile', {\n    name = 'Charlie',\n    age = 30,\n    level = 15\n})  -- User: Charlie | Age: 30 | Level: 17k\n```\n\nFormat modifiers:\n- `.q`: Quotes the value\n- `.d`: Decimal format\n- `.o`: Ordinal format\n\n### Pluralization\n\nsmiti18n implements the [CLDR plural rules](http://cldr.unicode.org/index/cldr-spec/plural-rules) for accurate pluralization across different languages. Each language can have different plural categories like 'one', 'few', 'many', and 'other'.\n\n#### Basic Usage\n```lua\ni18n = require 'smiti18n'\n\ni18n.load({\n  en = {\n    msg = {\n      one   = \"one message\",\n      other = \"%{count} messages\"\n    }\n  },\n  ru = {\n    msg = {\n      one   = \"1 сообщение\",\n      few   = \"%{count} сообщения\",  -- 2-4 messages\n      many  = \"%{count} сообщений\",  -- 5-20 messages\n      other = \"%{count} сообщения\"   -- fallback\n    }\n  }\n})\n\n-- English pluralization\ni18n.setLocale('en')\nprint(i18n('msg', {count = 1}))  -- \"one message\"\nprint(i18n('msg', {count = 5}))  -- \"5 messages\"\n\n-- Russian pluralization\ni18n.setLocale('ru')\nprint(i18n('msg', {count = 1}))  -- \"1 сообщение\"\nprint(i18n('msg', {count = 3}))  -- \"3 сообщения\"\nprint(i18n('msg', {count = 5}))  -- \"5 сообщений\"\n```\n\n**💡NOTE!** The `count` parameter is required for plural translations.\n\n#### Custom Pluralization Rules\n\nFor special cases or invented languages, you can define custom pluralization rules by specifying a custom pluralization function in the second parameter of `setLocale()`.\n\n```lua\n-- Custom pluralization for a constructed language\nlocal customPlural = function(n)\n  if n == 0 then return 'zero' end\n  if n == 1 then return 'one' end\n  if n \u003e 1000 then return 'many' end\n  return 'other'\nend\n\ni18n.setLocale('conlang', customPlural)\n```\n\nThis function must return a plural category when given a number.\nAvailable plural categories:\n- `zero`: For languages with special handling of zero\n- `one`: Singular form\n- `two`: Special form for two items\n- `few`: For languages with special handling of small numbers\n- `many`: For languages with special handling of large numbers\n- `other`: Default fallback form\n\n### Arrays\n\nTranslation values can be arrays for handling ordered collections of strings with support for interpolation and pluralization.\n\n```lua\ni18n.load({\n  en = {\n    -- Simple array of strings\n    greetings = {\"Hello!\", \"Hi there!\", \"Howdy!\"},\n\n    -- Get a random greeting\n    print(i18n('greetings')[math.random(#i18n('greetings'))])\n  }\n})\n```\n\n#### Features\n\nArrays support:\n\n- Plain strings\n- Interpolated values\n- Plural forms\n- Nested arrays\n- Mixed content types\n\n#### Common Use Cases\n\n1. **Dialogue Systems**\n```lua\ni18n.load({\n  en = {\n    dialogue = {\n      \"Detective: What brings you here?\",\n      \"Witness: I saw everything %{time}.\",\n      {\n        one = \"Detective: Just %{count} witness?\",\n        other = \"Detective: Already %{count} witnesses.\"\n      }\n    }\n  }\n})\n\n-- Play through dialogue sequence\nfor _, line in ipairs(i18n('dialogue', {time = \"last night\", count = 1})) do\n  print(line)\nend\n```\n\n2. **Tutorial Steps**\n\n```lua\ni18n.load({\n  en = {\n    tutorial = {\n      \"Welcome to %{game_name}!\",\n      {\n        one = \"You have %{lives} life - be careful!\",\n        other = \"You have %{lives} lives remaining.\"\n      },\n      \"Use WASD to move\",\n      \"Press SPACE to jump\"\n    }\n  }\n})\n```\n\n3. **Status Displays**\n\n```lua\ni18n.load({\n  en = {\n    status = {\n      \"=== Game Status ===\",\n      \"Player: %{name}\",\n      {\n        one = \"%{coins} coin collected\",\n        other = \"%{coins} coins collected\"\n      },\n      \"Level: %{level}\",\n      \"==================\"\n    }\n  }\n})\n```\n\n#### Tips\n- Arrays maintain their order\n- Access individual elements with numeric indices\n- Use `#` operator to get array length\n- Combine with `math.random()` for random selection\n- Arrays can be nested for complex dialogue trees\n\n### Formats\n\nThe library provides utilities for formatting numbers, prices and dates according to locale conventions. Formats are configured through locale files using the `_formats` key. If no locale-specific formats are defined, the library falls back to ISO standard formats.\n\n```lua\n-- Example locale file (en-UK.lua)\nreturn {\n  [\"en-UK\"] = {\n    _formats = {\n      currency = {\n        symbol = \"£\",\n        decimal_symbol = \".\",\n        thousand_separator = \",\",\n        positive_format = \"%c %p%q\"  -- £ 99.99\n      },\n      number = {\n        decimal_symbol = \".\",\n        thousand_separator = \",\"\n      },\n      date_time = {\n        long_date = \"%l %d %F %Y\",  -- Monday 25 March 2024\n        short_time = \"%H:%M\"        -- 15:45\n      }\n    }\n  }\n}\n```\n\n#### Usage\n\n```lua\ni18n.loadFile('locales/en-UK.lua')\ni18n.setLocale('en-UK')\n\n-- Numbers\nlocal num = i18n.formatNumber(1234.56)  -- \"1,234.56\"\n\n-- Currency\nlocal price = i18n.formatPrice(99.99)   -- \"£ 99.99\"\n\n-- Dates\nlocal date = i18n.formatDate(\"long_date\")  -- \"Monday 25 March 2024\"\n```\n\n#### ISO Fallbacks\n\nWhen no locale formats are configured, falls back to:\n\n- Numbers: ISO 31-0 (space separator, point decimal) - \"1 234.56\"\n- Currency: ISO 4217 (XXX symbol) - \"XXX 99.99\"\n- Dates: ISO 8601 - \"2024-03-25T15:45:30\"\n\n#### Format Patterns\n\nDates use [strftime](https://strftime.org/) codes: `%Y` year, `%m` month, `%d` day, `%H` hour, `%M` minute.\n\n- Currency patterns use:\n  - `%c` - currency symbol\n  - `%q` - formatted amount\n  - `%p` - polarity sign (+/-)\n\nFor a complete reference implementation of all format options, see [spec/en-UK.lua](https://github.com/Oval-Tutu/smiti18n/blob/master/spec/en-UK.lua).\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a pull request.\n\n## Specs\n\nThis project uses [busted](https://github.com/Olivine-Labs/busted) for its specs. If you want to run the specs, you will have to install it first. Then just execute the following from the root inspect folder:\n\n```shell\nbusted\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FOval-Tutu%2Fsmiti18n","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FOval-Tutu%2Fsmiti18n","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FOval-Tutu%2Fsmiti18n/lists"}