feat: add .properties file format plugin#4345
Conversation
Add @inlang/plugin-properties-file — a plugin that enables using Java .properties files for storing translations in inlang/Paraglide projects. Built on the `properties-file` npm package (https://github.com/properties-file/properties-file) for robust parsing of the .properties format including Unicode escapes, multiline values, and special characters. Features: - Import/export of .properties files (key = value format) - Variable interpolation with {variable} syntax - Inline comment support (comments preserved in source files) - Optional key sorting (ascending/descending) - Multiple path patterns for organizing translation files Configuration: ```json { "modules": ["@inlang/plugin-properties-file"], "plugin.inlang.propertiesFile": { "pathPattern": "./messages/{locale}.properties" } } ``` The .properties format is universally supported by every major TMS (Crowdin, Phrase, Lokalise, Transifex) and provides inline comments for translator context — a feature unavailable in JSON-based formats. 19 tests covering simple values, variables, multiple locales, roundtrip integrity, key sorting, special characters, unicode, and edge cases.
🦋 Changeset detectedLatest commit: 8d3dec1 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅ |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c493bc634b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
|
||
| const lines: string[] = []; | ||
| for (const entry of entries) { | ||
| lines.push(`${entry.key} = ${entry.value}`); |
There was a problem hiding this comment.
Escape serialized property values
The exporter writes raw text directly into .properties lines (key = value) without escaping control characters, so values containing newlines, trailing backslashes, or other syntax-significant characters are emitted as invalid/misparsed properties content. In practice this corrupts messages on roundtrip (for example, a pattern containing \n becomes multiple lines and changes key/value boundaries), so serialization needs proper .properties escaping instead of plain string concatenation.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in a295a33 — exported values now escape backslashes, newlines, carriage returns, and tabs via a dedicated escapePropertyValue() function. Added a test covering control character escaping through roundtrip.
| // Properties files only support single variants (no selectors) | ||
| const variant = variantsOfMessage[0]; | ||
| if (!variant) { |
There was a problem hiding this comment.
Reject multi-variant messages instead of truncating
For messages with selectors (plural/context/etc.), variants.filter(...) can return multiple variants but the exporter unconditionally picks variantsOfMessage[0] and drops the rest. This silently loses translations when saving any project that has more than one variant per message, so the code should either serialize all variants in a defined way or throw an explicit unsupported-feature error rather than exporting incomplete data.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in a295a33 — the exporter now throws an explicit error when a message has multiple variants that can't be represented in the .properties format.
To clarify: the .properties format can support plurals via ICU MessageFormat syntax within values (e.g., {count, plural, one {1 item} other {{count} items}}). In that model, plural forms live inside a single value string — every locale has the same set of keys, which avoids the key-mismatch problem that arises when plural forms are split across separate keys.
This constraint is not specific to .properties — any flat key-value format (including plain JSON) has the same limitation. The existing @inlang/plugin-json iterates all variants and writes them to the same key, meaning the last variant silently overwrites the others. We chose to throw an explicit error instead, which is the safer behavior — failing loudly rather than silently losing translation data.
This initial implementation treats values as opaque strings with {variable} interpolation. ICU MessageFormat parsing could be added as a follow-up. The error is a safety net for cases where the SDK hands the exporter multi-variant data from another plugin (e.g., plugin-icu1).
|
I have read the CLA Document and I hereby sign the CLA |
- Add changeset for version bump (minor) - Copy properties-file logo SVG to plugin assets - Add icon field to marketplace-manifest.json Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Escape control characters in exported values — backslashes, newlines, carriage returns, and tabs are now properly escaped when serializing to .properties format, preventing file corruption on roundtrip. 2. Throw explicit error on multi-variant messages — instead of silently dropping variants beyond the first, the exporter now throws a clear error explaining that .properties files do not support plural/select variants. Added 2 tests covering both fixes (21 total). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verify that ICU plural and select syntax in .properties values survives roundtrip as plain text. The plugin treats ICU syntax as opaque string content — it does not parse it into inlang's internal variant/selector model. This is correct behavior: ICU parsing is the responsibility of a dedicated ICU plugin, not the file format plugin. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@nbouvrette thanks for the PR! Are you willing to host the plugin in your own repo and I'll list it on inlang.com? I fear that i might need to maintain the Java properties plugin |
I didn't really think about this - I'm just starting to use |
|
@nbouvrette i'd prefer a separate repo. could you do that? |
|
@samuelstroschein turns out I put my localization project on hold for now. Should I close this PR? not sure when I will get back to it - I do think it would be useful for you to support .properties file on the other hand |
|
Closing for now then. Nothing against someone hosting the properties plugin themselves. I dont want to maintain it |
Summary
Adds
@inlang/plugin-properties-file— a plugin that enables using Java.propertiesfiles for storing translations in inlang/Paraglide projects.properties-filenpm package for robust parsing{variable}syntax (matching inlang message format conventions)\n,\r,\t,\\) in exported values.propertiesformat)Why .properties?
The
.propertiesformat is universally supported by every major TMS (Crowdin, Phrase, Lokalise, Transifex) — avoiding format lock-in. It also provides inline comments for translator context, a feature unavailable in JSON-based formats:Configuration
{ "modules": ["@inlang/plugin-properties-file"], "plugin.inlang.propertiesFile": { "pathPattern": "./messages/{locale}.properties" } }CI Note
The
@inlang/website-v2:lintfailure is a pre-existing issue on themainbranch (unused imports/declarations from the recent landing page rewrite). A separate fix has been submitted in #4346.Future work (not in this PR)
.propertiesfiles (e.g.,./src/components/Hero/{locale}.properties) via glob-based path patterns. This could eventually become a shared utility across format plugins.Test plan
tsc --noEmitpassespnpm run buildproduces dist/index.js🤖 Generated with Claude Code