Skip to content

Commit 8e97256

Browse files
committed
fix(compiler): dictionary merging
1 parent e1d8ecd commit 8e97256

File tree

4 files changed

+67
-48
lines changed

4 files changed

+67
-48
lines changed

.changeset/eleven-ghosts-roll.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"vite-project": patch
3+
"@lingo.dev/_compiler": patch
4+
"next-app": patch
5+
---
6+
7+
fix dictionary merging

demo/next-app/src/lingo/dictionary.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ export default {
55
entries: {
66
"0/declaration/body/0/argument": {
77
content: {
8-
ar: "قم بتعريب تطبيق React الخاص بك بجميع اللغات في دقائق.<element:br></element:br> توسع ليصل إلى الملايين منذ اليوم الأول.",
9-
de: "Lokalisieren Sie Ihre React-App in jeder Sprache innerhalb von Minuten.<element:br></element:br> Skalieren Sie vom ersten Tag an auf Millionen.",
10-
en: "Localiza tu aplicación React en todos los idiomas en minutos.<element:br></element:br> Escala a millones desde el primer día.",
8+
ar: "قم بتوطين تطبيق React الخاص بك بكل لغة في دقائق.<element:br></element:br> قم بالتوسع إلى الملايين من اليوم الأول.",
9+
de: "Lokalisieren Sie Ihre React-App in jeder Sprache in Minuten.<element:br></element:br> Skalieren Sie von Anfang an auf Millionen.",
10+
en: "Localize your React app in every language in minutes.<element:br></element:br> Scale to millions from day one.",
1111
es: "Localiza tu aplicación React en cualquier idioma en minutos.<element:br></element:br> Escala a millones desde el primer día.",
12-
fr: "Localisez votre application React dans toutes les langues en quelques minutes.<element:br></element:br> Adaptez-vous à des millions d'utilisateurs dès le premier jour.",
13-
ja: "数分でReactアプリをあらゆる言語にローカライズ。<element:br></element:br> 初日から数百万規模へスケール。",
14-
ko: "몇 분 안에 React 앱을 모든 언어로 현지화하세요.<element:br></element:br> 첫날부터 수백만 규모로 확장하세요.",
15-
ru: "Локализуйте своё React-приложение на любой язык за считанные минуты.<element:br></element:br> Масштабируйтесь до миллионов пользователей с первого дня.",
16-
zh: "在几分钟内将您的 React 应用程序本地化为每种语言。<element:br></element:br> 从第一天起就支持数百万用户的扩展。",
12+
fr: "Localisez votre application React dans toutes les langues en quelques minutes.<element:br></element:br> Évoluez vers des millions dès le premier jour.",
13+
ja: "数分でReactアプリをすべての言語にローカライズします。<element:br></element:br> 最初の日から数百万にスケールします。",
14+
ko: "몇 분 만에 React 앱을 모든 언어로 로컬라이즈하세요.<element:br></element:br> 첫날부터 수백만 사용자에게 확장할 수 있습니다.",
15+
ru: "Локализуйте ваше приложение React на любом языке за считанные минуты.<element:br></element:br> Масштабируйте до миллионов с первого дня.",
16+
zh: "在几分钟内将您的React应用本地化为任何语言。<element:br></element:br> 从第一天开始扩展到数百万用户。",
1717
},
1818
hash: "2b8865d21f308426a4da45852b27004d",
1919
},

demo/vite-project/src/lingo/dictionary.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ export default {
5353
es: "¡Bienvenido a tu nueva aplicación de Vite y React! Esta plantilla de inicio incluye todo lo que necesitas para comenzar con Vite y React y Lingo.dev para la internacionalización.",
5454
fr: "Bienvenue dans votre nouvelle application Vite & React ! Ce modèle de démarrage comprend tout ce dont vous avez besoin pour commencer avec Vite & React et Lingo.dev pour l'internationalisation.",
5555
ja: "新しいVite&Reactアプリケーションへようこそ!このスターターテンプレートには、Vite&ReactとLingo.devを使用した国際化を始めるために必要なすべてが含まれています。",
56-
ko: "Vite & React 애플리케이션에 오신 것을 환영합니다! 이 스타터 템플릿에는 Vite & React 및 국제화를 위한 Lingo.dev로 시작하는 데 필요한 모든 것이 포함되어 있습니다.",
56+
ko: "새로운 Vite & React 애플리케이션에 오신 것을 환영합니다! 이 스타터 템플릿에는 Vite & React 및 국제화를 위한 Lingo.dev로 시작하는 데 필요한 모든 것이 포함되어 있습니다.",
5757
ru: "Добро пожаловать в ваше новое приложение на Vite и React! Этот стартовый шаблон включает всё необходимое для начала работы с Vite, React и Lingo.dev для интернационализации.",
58-
zh: "欢迎使用全新的 Vite 和 React 应用程序!此入门模板包含了使用 Vite 和 React 以及 Lingo.dev 进行国际化所需的一切。",
58+
zh: "欢迎使用您的全新 Vite 和 React 应用程序!此入门模板包含了使用 Vite 和 React 以及 Lingo.dev 进行国际化所需的一切。",
5959
},
6060
hash: "c6deea18ed327336cf526f7d5a268d18",
6161
},
@@ -95,7 +95,7 @@ export default {
9595
es: "Haz clic en los logos de arriba para obtener más información",
9696
fr: "Cliquez sur les logos ci-dessus pour en savoir plus",
9797
ja: "詳細を学ぶには上記のロゴをクリックしてください",
98-
ko: "위의 로고를 클릭하여 자세히 알아보세요",
98+
ko: "더 자세히 알아보려면 위의 로고를 클릭하세요",
9999
ru: "Нажмите на логотипы выше, чтобы узнать больше",
100100
zh: "点击上方的标志以了解更多信息",
101101
},

packages/compiler/src/lib/lcp/cache.ts

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -57,53 +57,65 @@ export class LCPCache {
5757
dictionary: DictionarySchema,
5858
lcp: LCPSchema,
5959
): DictionaryCacheSchema {
60-
const files = _(dictionary.files)
61-
.mapValues((file, fileName) => ({
62-
...file,
63-
entries: _(file.entries)
64-
.mapValues((entry, entryName) => {
65-
// find if entry exists in current cache, it might contain some locales already
66-
const cachedEntry =
67-
_.get(currentCache, ["files", fileName, "entries", entryName]) ??
68-
{};
69-
const hash = _.get(lcp, [
70-
"files",
71-
fileName,
72-
"scopes",
73-
entryName,
74-
"hash",
75-
]);
76-
77-
// reuse existing cache entry if its hash matches LCP schema, ensures the cache is up to date
78-
const cachedEntryContent =
79-
cachedEntry.hash === hash ? cachedEntry.content : {};
80-
81-
// sorted by keys (locales) to minimize diffs
82-
const content = _({
83-
...cachedEntryContent,
84-
[dictionary.locale]: entry,
85-
})
86-
.toPairs()
87-
.sortBy([0])
88-
.fromPairs()
89-
.value();
90-
return { content, hash };
91-
})
60+
// Deep-clone to avoid mutating caller's object
61+
const newCache: DictionaryCacheSchema = _.cloneDeep(currentCache);
62+
63+
for (const [fileName, fileData] of Object.entries(dictionary.files)) {
64+
for (const [entryName, entryValue] of Object.entries(fileData.entries)) {
65+
const hash = _.get(lcp, [
66+
"files",
67+
fileName,
68+
"scopes",
69+
entryName,
70+
"hash",
71+
]);
72+
73+
const existingEntry = _.get(newCache, [
74+
"files",
75+
fileName,
76+
"entries",
77+
entryName,
78+
]) || {
79+
content: {},
80+
hash,
81+
};
82+
83+
// Merge locales and sort them alphabetically to stabilise diffs
84+
const mergedContent = _({
85+
...existingEntry.content,
86+
[dictionary.locale]: entryValue,
87+
})
9288
.toPairs()
9389
.sortBy([0])
9490
.fromPairs()
95-
.value(),
96-
}))
91+
.value();
92+
93+
_.set(newCache, ["files", fileName, "entries", entryName], {
94+
content: mergedContent,
95+
hash,
96+
});
97+
}
98+
}
99+
100+
// Final sort of files and their entries for minimal diffs
101+
const sortedFiles = _(newCache.files)
97102
.toPairs()
98103
.sortBy([0])
104+
.map(([fileName, file]) => {
105+
const sortedEntries = _(file.entries)
106+
.toPairs()
107+
.sortBy([0])
108+
.fromPairs()
109+
.value();
110+
return [fileName, { entries: sortedEntries }];
111+
})
99112
.fromPairs()
100113
.value();
101114

102-
const newCache = {
115+
return {
103116
version: dictionary.version,
104-
files,
117+
files: sortedFiles,
105118
};
106-
return newCache;
107119
}
108120

109121
// extract dictionary from cache for given locale, validate entry hash from LCP schema

0 commit comments

Comments
 (0)