-
-
Notifications
You must be signed in to change notification settings - Fork 59
feat(i18n): add keyStyle for dotted keys #160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Reviewer's GuideImplements a configurable keyStyle option for diffing i18n JSON files, allowing control over whether dotted keys are treated as nested paths, flattened, or preserved, and wires this behavior through diff usage, config types, and documentation with accompanying tests. Sequence diagram for diff usage with configurable keyStylesequenceDiagram
participant TranslateLocale
participant SplitJsonToChunks
participant Diff
participant JustDiff
participant LodashSet as Lodash_set_unset
TranslateLocale->>Diff: diff(entryObj, targetObj, config)
Diff->>JustDiff: diff(targetObj, entryObj)
JustDiff-->>Diff: diffResult
loop for each remove item
Diff->>Diff: resolveDiffPath(targetObj, item.path, config.keyStyle)
Diff->>LodashSet: unset(cloneTarget, resolvedPath)
end
loop for each add item
Diff->>Diff: resolveDiffPath(entryObj, item.path, config.keyStyle)
Diff->>LodashSet: set(extra, resolvedPath, item.value)
end
Diff-->>TranslateLocale: { entry, target }
SplitJsonToChunks->>Diff: diff(entry, target, config)
Diff-->>SplitJsonToChunks: { entry: extraJSON, target }
SplitJsonToChunks->>SplitJsonToChunks: splitJSONtoSmallChunks(extraJSON, splitToken)
Class diagram for updated i18n config and diff utilitiesclassDiagram
class KeyStyle {
<<type>>
+auto
+flat
+nested
}
class I18nConfigLocale {
+string entry
+string entryLocale
+string modelName
+string output
+string[] outputLocales
+KeyStyle keyStyle
+string reference
+boolean saveImmediately
+number splitToken
}
class DiffUtil {
+diff(entry: LocaleObj, target: LocaleObj, config: I18nConfig) LocaleObj
+resolveDiffPath(source: LocaleObj, path: DiffPath, keyStyle: KeyStyle) DiffPath
+hasOwnKey(obj: LocaleObj, key: string) boolean
}
class LocaleObj {
<<type>>
}
class DiffPath {
<<type>>
+string
+Array~number|string~
}
class I18nConfig {
+I18nConfigLocale localeConfig
}
I18nConfigLocale --> KeyStyle : uses
I18nConfig --> I18nConfigLocale : contains
DiffUtil --> LocaleObj : uses
DiffUtil --> DiffPath : uses
DiffUtil --> KeyStyle : uses
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
👍 @Innei Thank you for raising your pull request and contributing to our Community |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a keyStyle configuration option to control how dot-delimited keys are resolved when building translation diff entries. The feature supports three modes: nested (default, for backward compatibility), flat (converts all nested paths to dot notation), and auto (preserves the original key style from entry files).
Key Changes
- Added
KeyStyletype andkeyStyleconfiguration property to handle different key resolution strategies - Updated
diff()function to accept and apply keyStyle configuration when resolving paths - Added test coverage for flat and auto modes with nested object structures
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/lobe-i18n/src/types/config.ts | Defines KeyStyle type and adds keyStyle property to I18nConfigLocale interface |
| packages/lobe-i18n/src/utils/diffJson.ts | Implements resolveDiffPath function and updates diff to support keyStyle modes |
| packages/lobe-i18n/src/utils/diffJson.test.ts | Adds test cases for flat and auto keyStyle modes |
| packages/lobe-i18n/src/utils/splitJsonToChunks.ts | Passes config to diff function to enable keyStyle support |
| packages/lobe-i18n/src/commands/TranslateLocale/index.tsx | Updates diff calls to pass config parameter in both folder and flat query modes |
| packages/lobe-i18n/README.md | Documents keyStyle configuration option in English |
| packages/lobe-i18n/README.zh-CN.md | Documents keyStyle configuration option in Chinese |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (keyStyle === 'flat') { | ||
| return [path.join('.')]; | ||
| } | ||
| if (keyStyle === 'auto') { | ||
| const joinedPath = path.join('.'); | ||
| if (hasOwnKey(source, joinedPath)) return [joinedPath]; | ||
| } |
Copilot
AI
Dec 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The flat mode converts array paths to dotted strings using join('.'), which may cause issues when the path includes array indices. For example, a path ['array', 3] (representing array[3]) would become ['array.3'], creating a literal object key "array.3" instead of preserving the array structure. Consider checking if any path elements are numbers (array indices) and handling them specially to preserve array semantics.
| if (keyStyle === 'flat') { | |
| return [path.join('.')]; | |
| } | |
| if (keyStyle === 'auto') { | |
| const joinedPath = path.join('.'); | |
| if (hasOwnKey(source, joinedPath)) return [joinedPath]; | |
| } | |
| const hasNumericSegment = path.some((segment) => typeof segment === 'number'); | |
| if (keyStyle === 'flat') { | |
| // In flat mode, only join segments when all are strings to avoid | |
| // turning array indices into dotted string keys (e.g. ['array', 3] -> 'array.3'). | |
| if (!hasNumericSegment) { | |
| return [path.join('.')]; | |
| } | |
| return path; | |
| } | |
| if (keyStyle === 'auto') { | |
| // In auto mode, prefer a flat key only when all path segments are strings | |
| // and such a key actually exists on the source object. | |
| if (!hasNumericSegment) { | |
| const joinedPath = path.join('.'); | |
| if (hasOwnKey(source, joinedPath)) return [joinedPath]; | |
| } | |
| } |
| it('should flatten nested paths when keyStyle is flat', () => { | ||
| const entry = { | ||
| a: { | ||
| bc: { | ||
| b: 'value', | ||
| }, | ||
| }, | ||
| }; | ||
| const target = { | ||
| a: { | ||
| bc: {}, | ||
| }, | ||
| }; | ||
|
|
||
| const result = diff(entry, target, { keyStyle: 'flat' }); | ||
|
|
||
| expect(result.entry).toEqual({ | ||
| 'a.bc.b': 'value', | ||
| }); | ||
| }); | ||
|
|
||
| it('should preserve mixed keys when keyStyle is auto', () => { | ||
| const entry = { | ||
| 'a.bc.b': 'value', | ||
| 'nested': { | ||
| key: 'value2', | ||
| }, | ||
| }; | ||
| const target = {}; | ||
|
|
||
| const result = diff(entry, target, { keyStyle: 'auto' }); | ||
|
|
||
| expect(result.entry).toEqual({ | ||
| 'a.bc.b': 'value', | ||
| 'nested': { | ||
| key: 'value2', | ||
| }, | ||
| }); | ||
| }); |
Copilot
AI
Dec 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test coverage for the new keyStyle modes (flat and auto) should include test cases with arrays to ensure the behavior is correct when dealing with array indices. Currently, only the default nested mode has an array test case (line 260-276).
| */ | ||
| entryLocale: string; | ||
| /** | ||
| * @description How to resolve dot-delimited keys when building diff entries |
Copilot
AI
Dec 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The JSDoc description for keyStyle could be more detailed to help developers understand what each mode does. Consider expanding it to explain: nested treats dots as path separators (a.b.c becomes {a: {b: {c: value}}}), flat preserves dotted keys as literal strings, and auto detects whether the entry file uses dotted keys or nested structure and preserves that format.
| * @description How to resolve dot-delimited keys when building diff entries | |
| * @description How to resolve dot-delimited keys when building diff entries. | |
| * - `nested`: treats dots as path separators (e.g. `a.b.c` becomes `{ a: { b: { c: value } } }`). | |
| * - `flat`: preserves dotted keys as literal strings (e.g. `a.b.c` stays as the single key `"a.b.c"`). | |
| * - `auto`: detects whether the entry file uses dotted keys or nested objects and preserves that format. |
|
❤️ Great PR @Innei ❤️ The growth of project is inseparable from user feedback and contribution, thanks for your contribution! |
## [Version 1.26.0](https://github.com/lobehub/lobe-cli-toolbox/compare/@lobehub/[email protected]...@lobehub/[email protected]) <sup>Released on **2025-12-25**</sup> #### ✨ Features - **misc**: Update models info and add keyStyle for i18n dotted keys. <br/> <details> <summary><kbd>Improvements and Fixes</kbd></summary> #### What's improved * **misc**: Update models info and add keyStyle for i18n dotted keys, closes [#160](#160) ([49cab44](49cab44)) </details> <div align="right"> [](#readme-top) </div>
|
🎉 This PR is included in version 1.26.0 🎉 The release is available on npm package (@latest dist-tag) Your semantic-release bot 📦🚀 |
Summary
keyStyleconfiguration option to control how dot-delimited keys are resolved when building diff entriesnested(default): treats dotted keys as nested pathsflat: flattens all nested paths to dot notationauto: preserves the original key style from the entry fileTest plan
flatandautokey styles indiffJson.test.tsnested)🤖 Generated with Claude Code
Summary by Sourcery
Add configurable key style handling for dotted i18n keys and propagate it through diffing and translation workflows.
New Features:
Enhancements:
Documentation:
Tests: