Skip to content

Commit 36d1544

Browse files
Major version check
1 parent 045cab2 commit 36d1544

4 files changed

Lines changed: 121 additions & 27 deletions

File tree

core/ecschema-metadata/src/Localization/SchemaLocalization.ts

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -50,43 +50,72 @@ export class SchemaLocalization {
5050
return this._provider;
5151
}
5252

53+
/**
54+
* Provide major version of localized schema
55+
* @param version The version string.
56+
* @returns Major version number, or undefined
57+
*/
58+
private getMajorVersion(version: string): number | undefined {
59+
const rawVersion = version.split(".");
60+
if (rawVersion.length > 0) {
61+
const majorVersion = parseInt(rawVersion[0], 10);
62+
return isNaN(majorVersion) ? undefined : majorVersion;
63+
}
64+
65+
return undefined;
66+
}
67+
5368
/**
5469
* Load and cache the localization JSON for a schema.
55-
* @param schemaName The name of the schema
70+
* @param schema The schema to load localization for
5671
* @returns The localization JSON, or null if not found
5772
* @internal
5873
*/
59-
private async getSchemaLocalizationJson(schemaName: string): Promise<SchemaLocalizationJson | null> {
60-
const cacheKey = `${schemaName}:${this._locale}`;
74+
private async getSchemaLocalizationJson(schema: Schema): Promise<SchemaLocalizationJson | null> {
75+
const cacheKey = `${schema.name}:${this._locale}`;
6176

6277
if (this._cache.has(cacheKey)) {
6378
return this._cache.get(cacheKey) || null;
6479
}
6580

66-
const localization = await this._provider.getLocalization(schemaName, this._locale);
81+
const localization = await this._provider.getLocalization(schema.name, this._locale);
82+
if (localization && localization.version) {
83+
const localizationMajor = this.getMajorVersion(localization.version);
84+
if (schema.schemaKey.readVersion !== localizationMajor) {
85+
throw new Error(`Localization version mismatch for schema "${schema.name}". Schema major version is ${schema.schemaKey.readVersion.toString()}, but localization is for major version ${localizationMajor}.`);
86+
}
87+
}
88+
6789
this._cache.set(cacheKey, localization || null);
6890
return localization || null;
6991
}
7092

7193
/**
7294
* Load the base locale localization if the current locale has a region (e.g., "es" from "es-CO").
73-
* @param schemaName The name of the schema
95+
* @param schema The schema to load localization for
7496
* @returns The base localization JSON, or null if not found or not applicable
7597
* @internal
7698
*/
77-
private async getBaseLocalizationJson(schemaName: string): Promise<SchemaLocalizationJson | null> {
99+
private async getBaseLocalizationJson(schema: Schema): Promise<SchemaLocalizationJson | null> {
78100
if (!this._locale.includes("-")) {
79101
return null;
80102
}
81103

82104
const baseLocale = this._locale.split("-")[0];
83-
const cacheKey = `${schemaName}:${baseLocale}`;
105+
const cacheKey = `${schema.name}:${baseLocale}`;
84106

85107
if (this._cache.has(cacheKey)) {
86108
return this._cache.get(cacheKey) || null;
87109
}
88110

89-
const localization = await this._provider.getLocalization(schemaName, baseLocale);
111+
const localization = await this._provider.getLocalization(schema.name, baseLocale);
112+
if (localization && localization.version) {
113+
const localizationMajor = this.getMajorVersion(localization.version);
114+
if (schema.schemaKey.readVersion !== localizationMajor) {
115+
throw new Error(`Localization version mismatch for schema "${schema.name}". Schema major version is ${schema.schemaKey.readVersion.toString()}, but localization is for major version ${localizationMajor}.`);
116+
}
117+
}
118+
90119
this._cache.set(cacheKey, localization || null);
91120
return localization || null;
92121
}
@@ -96,14 +125,14 @@ export class SchemaLocalization {
96125
* Fallback: localized label (specific locale) → localized label (base locale) → original label → schema name
97126
*/
98127
public async getSchemaLabel(schema: Schema): Promise<string> {
99-
const localization = await this.getSchemaLocalizationJson(schema.name);
128+
const localization = await this.getSchemaLocalizationJson(schema);
100129

101130
if (localization?.label) {
102131
return localization.label;
103132
}
104133

105134
// Try base locale if specific locale didn't have the schema label
106-
const baseLocalization = await this.getBaseLocalizationJson(schema.name);
135+
const baseLocalization = await this.getBaseLocalizationJson(schema);
107136
if (baseLocalization?.label) {
108137
return baseLocalization.label;
109138
}
@@ -116,14 +145,14 @@ export class SchemaLocalization {
116145
* Fallback: localized description (specific locale) → localized description (base locale) → original description
117146
*/
118147
public async getSchemaDescription(schema: Schema): Promise<string | undefined> {
119-
const localization = await this.getSchemaLocalizationJson(schema.name);
148+
const localization = await this.getSchemaLocalizationJson(schema);
120149

121150
if (localization?.description) {
122151
return localization.description;
123152
}
124153

125154
// Try base locale if specific locale didn't have the schema description
126-
const baseLocalization = await this.getBaseLocalizationJson(schema.name);
155+
const baseLocalization = await this.getBaseLocalizationJson(schema);
127156
if (baseLocalization?.description) {
128157
return baseLocalization.description;
129158
}
@@ -136,7 +165,7 @@ export class SchemaLocalization {
136165
* Fallback: localized label (specific locale) → localized label (base locale) → original label → item name
137166
*/
138167
public async getSchemaItemLabel(item: SchemaItem): Promise<string> {
139-
const localization = await this.getSchemaLocalizationJson(item.schema.name);
168+
const localization = await this.getSchemaLocalizationJson(item.schema);
140169

141170
if (localization) {
142171
const localizedText = this.findItemLocalization(localization, item);
@@ -146,7 +175,7 @@ export class SchemaLocalization {
146175
}
147176

148177
// Try base locale if specific locale didn't have the item
149-
const baseLocalization = await this.getBaseLocalizationJson(item.schema.name);
178+
const baseLocalization = await this.getBaseLocalizationJson(item.schema);
150179
if (baseLocalization) {
151180
const localizedText = this.findItemLocalization(baseLocalization, item);
152181
if (localizedText?.label) {
@@ -162,7 +191,7 @@ export class SchemaLocalization {
162191
* Fallback: localized description (specific locale) → localized description (base locale) → original description
163192
*/
164193
public async getSchemaItemDescription(item: SchemaItem): Promise<string | undefined> {
165-
const localization = await this.getSchemaLocalizationJson(item.schema.name);
194+
const localization = await this.getSchemaLocalizationJson(item.schema);
166195

167196
if (localization) {
168197
const localizedText = this.findItemLocalization(localization, item);
@@ -172,7 +201,7 @@ export class SchemaLocalization {
172201
}
173202

174203
// Try base locale if specific locale didn't have the item
175-
const baseLocalization = await this.getBaseLocalizationJson(item.schema.name);
204+
const baseLocalization = await this.getBaseLocalizationJson(item.schema);
176205
if (baseLocalization) {
177206
const localizedText = this.findItemLocalization(baseLocalization, item);
178207
if (localizedText?.description) {
@@ -188,15 +217,15 @@ export class SchemaLocalization {
188217
* Fallback: localized label (specific locale) → localized label (base locale) → original label → property name
189218
*/
190219
public async getPropertyLabel(ecClass: ECClass, property: Property): Promise<string> {
191-
const localization = await this.getSchemaLocalizationJson(ecClass.schema.name);
220+
const localization = await this.getSchemaLocalizationJson(ecClass.schema);
192221

193222
const localizedLabel = localization?.classes?.[ecClass.name]?.properties?.[property.name]?.label;
194223
if (localizedLabel) {
195224
return localizedLabel;
196225
}
197226

198227
// Try base locale if specific locale didn't have the property
199-
const baseLocalization = await this.getBaseLocalizationJson(ecClass.schema.name);
228+
const baseLocalization = await this.getBaseLocalizationJson(ecClass.schema);
200229
const baseLocalizedLabel = baseLocalization?.classes?.[ecClass.name]?.properties?.[property.name]?.label;
201230
if (baseLocalizedLabel) {
202231
return baseLocalizedLabel;
@@ -210,15 +239,15 @@ export class SchemaLocalization {
210239
* Fallback: localized description (specific locale) → localized description (base locale) → original description
211240
*/
212241
public async getPropertyDescription(ecClass: ECClass, property: Property): Promise<string | undefined> {
213-
const localization = await this.getSchemaLocalizationJson(ecClass.schema.name);
242+
const localization = await this.getSchemaLocalizationJson(ecClass.schema);
214243

215244
const localizedDescription = localization?.classes?.[ecClass.name]?.properties?.[property.name]?.description;
216245
if (localizedDescription) {
217246
return localizedDescription;
218247
}
219248

220249
// Try base locale if specific locale didn't have the property
221-
const baseLocalization = await this.getBaseLocalizationJson(ecClass.schema.name);
250+
const baseLocalization = await this.getBaseLocalizationJson(ecClass.schema);
222251
const baseLocalizedDescription = baseLocalization?.classes?.[ecClass.name]?.properties?.[property.name]?.description;
223252
if (baseLocalizedDescription) {
224253
return baseLocalizedDescription;
@@ -232,15 +261,15 @@ export class SchemaLocalization {
232261
* Fallback: localized label (specific locale) → localized label (base locale) → original label → enumerator name
233262
*/
234263
public async getEnumeratorLabel(enumeration: Enumeration, enumeratorName: string): Promise<string> {
235-
const localization = await this.getSchemaLocalizationJson(enumeration.schema.name);
264+
const localization = await this.getSchemaLocalizationJson(enumeration.schema);
236265

237266
const localizedLabel = localization?.enumerations?.[enumeration.name]?.enumerators?.[enumeratorName]?.label;
238267
if (localizedLabel) {
239268
return localizedLabel;
240269
}
241270

242271
// Try base locale if specific locale didn't have the enumerator
243-
const baseLocalization = await this.getBaseLocalizationJson(enumeration.schema.name);
272+
const baseLocalization = await this.getBaseLocalizationJson(enumeration.schema);
244273
const baseLocalizedLabel = baseLocalization?.enumerations?.[enumeration.name]?.enumerators?.[enumeratorName]?.label;
245274
if (baseLocalizedLabel) {
246275
return baseLocalizedLabel;
@@ -256,15 +285,15 @@ export class SchemaLocalization {
256285
* Fallback: localized description (specific locale) → localized description (base locale) → original description
257286
*/
258287
public async getEnumeratorDescription(enumeration: Enumeration, enumeratorName: string): Promise<string | undefined> {
259-
const localization = await this.getSchemaLocalizationJson(enumeration.schema.name);
288+
const localization = await this.getSchemaLocalizationJson(enumeration.schema);
260289

261290
const localizedDescription = localization?.enumerations?.[enumeration.name]?.enumerators?.[enumeratorName]?.description;
262291
if (localizedDescription) {
263292
return localizedDescription;
264293
}
265294

266295
// Try base locale if specific locale didn't have the enumerator
267-
const baseLocalization = await this.getBaseLocalizationJson(enumeration.schema.name);
296+
const baseLocalization = await this.getBaseLocalizationJson(enumeration.schema);
268297
const baseLocalizedDescription = baseLocalization?.enumerations?.[enumeration.name]?.enumerators?.[enumeratorName]?.description;
269298
if (baseLocalizedDescription) {
270299
return baseLocalizedDescription;

core/ecschema-metadata/src/test/Localization/SchemaLocalization.test.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -801,8 +801,8 @@ describe("SchemaLocalization", () => {
801801
});
802802

803803
it("should fall back to original labels when locale not available for any schema", async () => {
804-
// French (fr) not provided for any schema
805-
const localization = new SchemaLocalization(provider, "fr");
804+
// Urdu (ur) not provided for any schema
805+
const localization = new SchemaLocalization(provider, "ur");
806806

807807
const buildingLabel = await localization.getSchemaLabel(testBuildingSchema);
808808
expect(buildingLabel).to.equal("Test Building Schema");
@@ -827,6 +827,17 @@ describe("SchemaLocalization", () => {
827827

828828
await expect(localization.getSchemaLabel(testBuildingSchema)).rejects.toThrow("Invalid localization JSON");
829829
});
830+
831+
it("should throw error, when major version is different", async () => {
832+
const localization = new SchemaLocalization(provider, "fr");
833+
834+
// TestProduct schema and its French localization have same major version
835+
const productLabel = await localization.getSchemaLabel(testProductSchema);
836+
expect(productLabel).to.equal("Schéma de test de produit");
837+
expect(testProductSchema.label).to.equal("Test Product Schema");
838+
839+
// TestPerson schema and its French localization have different major version
840+
await expect(localization.getSchemaLabel(testPersonSchema)).rejects.toThrow(`Localization version mismatch for schema "TestPerson". Schema major version is 1, but localization is for major version 2.`);
841+
});
830842
});
831843
});
832-
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "ecschema-localization-v1",
3+
"name": "TestPerson",
4+
"version": "02.XX.XX",
5+
"locale": "fr",
6+
"label": "Schéma de test de personne",
7+
"description": "Un schéma de test pour les données personnelles",
8+
"classes": {
9+
"Person": {
10+
"label": "Personne",
11+
"description": "Une entité personne",
12+
"properties": {
13+
"FirstName": {
14+
"label": "Prénom",
15+
"description": "Le prénom"
16+
}
17+
}
18+
}
19+
}
20+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"$schema": "ecschema-localization-v1",
3+
"name": "TestProduct",
4+
"version": "01.XX.XX",
5+
"locale": "fr",
6+
"label": "Schéma de test de produit",
7+
"description": "Un schéma de test pour le catalogue de produits",
8+
"classes": {
9+
"Product": {
10+
"label": "Produit",
11+
"description": "Un article de produit",
12+
"properties": {
13+
"Name": {
14+
"label": "Nom",
15+
"description": "Le nom du produit"
16+
},
17+
"Price": {
18+
"label": "Prix",
19+
"description": "Le prix du produit"
20+
}
21+
}
22+
},
23+
"Category": {
24+
"label": "Catégorie",
25+
"description": "Une catégorie de produit",
26+
"properties": {
27+
"Name": {
28+
"label": "Nom",
29+
"description": "Le nom de la catégorie"
30+
}
31+
}
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)