Skip to content

Commit 5af2e75

Browse files
authored
[🔥AUDIT🔥] Fix up plural form fallback when no translation is present. (#2374)
🖍 _This is an audit!_ 🖍 ## Summary: I may have introduced this in my last fix, actually - but this now helps us ensure that we don't accidentally use the other language pluralization rules when we are falling back to English strings. Issue: FEI-6007 ## Test plan: The tests pass! Author: jeresig Auditors: #wonder-blocks Required Reviewers: Approved By: Checks: ✅ Chromatic - Get results on regular PRs (ubuntu-latest, 20.x), ✅ Test / Test (ubuntu-latest, 20.x, 2/2), ✅ Test / Test (ubuntu-latest, 20.x, 1/2), ✅ Lint / Lint (ubuntu-latest, 20.x), ✅ Check build sizes (ubuntu-latest, 20.x), ✅ Chromatic - Build on regular PRs / chromatic (ubuntu-latest, 20.x), ⏭️ Chromatic - Skip on Release PR (changesets), ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Prime node_modules cache for primary configuration (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ gerald, ⏭️ dependabot Pull Request URL: #2374
1 parent ebcadd4 commit 5af2e75

File tree

6 files changed

+54
-33
lines changed

6 files changed

+54
-33
lines changed

.changeset/dry-kangaroos-boil.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/wonder-blocks-i18n": patch
3+
---
4+
5+
Fix a bug with pluralization for fallback untranslated languages.

packages/wonder-blocks-i18n/src/functions/__tests__/i18n-store.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ describe("getPluralTranslation", () => {
8484
// Act
8585
const result = getPluralTranslation(
8686
{
87-
lang: TEST_LOCALE,
87+
lang: "en",
8888
messages: ["test singular", "test plural"],
8989
},
90-
0,
90+
1,
9191
);
9292

9393
// Assert
@@ -104,10 +104,10 @@ describe("getPluralTranslation", () => {
104104
// Act
105105
const result = getPluralTranslation(
106106
{
107-
lang: TEST_LOCALE,
107+
lang: "en",
108108
messages: ["test singular", "test plural"],
109109
},
110-
1,
110+
2,
111111
);
112112

113113
// Assert
@@ -135,10 +135,10 @@ describe("getPluralTranslation", () => {
135135
// Act
136136
const result = getPluralTranslation(
137137
{
138-
lang: TEST_LOCALE,
138+
lang: "en",
139139
messages: ["test singular", "test plural"],
140140
},
141-
0,
141+
1,
142142
);
143143

144144
// Assert

packages/wonder-blocks-i18n/src/functions/__tests__/i18n.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,17 @@ describe("i18n", () => {
142142
expect(result).toMatchInlineSnapshot(`"arrrr mateys"`);
143143
});
144144

145+
it("ngettext should handle missing translations", () => {
146+
// Arrange
147+
jest.spyOn(Locale, "getLocale").mockImplementation(() => "ru");
148+
149+
// Act
150+
const result = ngettext("Singular", "Plural", 0);
151+
152+
// Assert
153+
expect(result).toMatchInlineSnapshot(`"Plural"`);
154+
});
155+
145156
it("doNotTranslate should not translate", () => {
146157
// Arrange
147158
loadTranslations(TEST_LOCALE, {

packages/wonder-blocks-i18n/src/functions/i18n-store.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
*/
66
import FakeTranslate from "./i18n-faketranslate";
77
import {getLocale} from "./locale";
8+
import {allPluralForms} from "./plural-forms";
89
import {PluralConfigurationObject} from "./types";
910

11+
type Language = keyof typeof allPluralForms;
12+
1013
// The cache of strings that have been translated, by locale.
1114
const localeMessageStore = new Map<
1215
string,
@@ -18,6 +21,20 @@ const localeMessageStore = new Map<
1821
// Create a fake translate object to use if we can't find a translation.
1922
const {translate: fakeTranslate} = new FakeTranslate();
2023

24+
/*
25+
* Return the ngettext position that matches the given number and lang.
26+
*
27+
* Arguments:
28+
* - num: The number upon which to toggle the plural forms.
29+
* - lang: The language to use as the basis for the pluralization.
30+
*/
31+
export const ngetpos = function (num: number, lang?: Language): number {
32+
const pluralForm = (lang && allPluralForms[lang]) || allPluralForms["en"];
33+
const pos = pluralForm(num);
34+
// Map true to 1 and false to 0, keep any numeric return value the same.
35+
return pos === true ? 1 : pos ? pos : 0;
36+
};
37+
2138
/**
2239
* Get the translation for a given id and locale.
2340
*
@@ -89,12 +106,15 @@ export const getSingularTranslation = (
89106
*/
90107
export const getPluralTranslation = (
91108
pluralConfig: PluralConfigurationObject,
92-
idx: number,
109+
num: number,
93110
) => {
94111
const {lang, messages} = pluralConfig;
95112

96113
// We try to find the translation in the cache.
97-
const translatedMessages = getTranslationFromStore(messages[0], lang);
114+
const translatedMessages = getTranslationFromStore(
115+
messages[0],
116+
getLocale(),
117+
);
98118

99119
// We found the translation so we can return the right plural form.
100120
if (translatedMessages) {
@@ -103,10 +123,13 @@ export const getPluralTranslation = (
103123
// just in case.
104124
return translatedMessages;
105125
}
126+
// Get the translated string
127+
const idx = ngetpos(num, getLocale());
106128
return translatedMessages[idx];
107129
}
108130

109131
// Otherwise, there's no translation, so we try to do fake translation.
132+
const idx = ngetpos(num, lang);
110133
return fakeTranslate(messages[idx]);
111134
};
112135

packages/wonder-blocks-i18n/src/functions/i18n.ts

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
/* To fix, remove an entry above, run ka-lint, and fix errors. */
44
import * as React from "react";
55

6-
import {allPluralForms} from "./plural-forms";
76
import {getLocale} from "./locale";
87
import {PluralConfigurationObject} from "./types";
98
import {getPluralTranslation, getSingularTranslation} from "./i18n-store";
@@ -47,8 +46,6 @@ interface _Overloads {
4746
): string;
4847
}
4948

50-
type Language = keyof typeof allPluralForms;
51-
5249
const interpolationMarker = /%\(([\w_]+)\)s/g;
5350

5451
/**
@@ -210,7 +207,7 @@ export const ngettext: ngettextOverloads = (
210207
typeof singular === "object"
211208
? singular
212209
: {
213-
lang: getLocale(),
210+
lang: "en",
214211
// We know plural is a string if singular is not a config object
215212
messages: [singular, plural as any],
216213
};
@@ -220,11 +217,7 @@ export const ngettext: ngettextOverloads = (
220217
const actualOptions: NGetOptions =
221218
(typeof singular === "object" ? num : (options as any)) || {};
222219

223-
// Get the translated string
224-
const idx = ngetpos(actualNum, pluralConfObj.lang);
225-
226-
// The common (non-error) case is messages[idx].
227-
const translation = getPluralTranslation(pluralConfObj, idx);
220+
const translation = getPluralTranslation(pluralConfObj, actualNum);
228221

229222
// Get the options to substitute into the string.
230223
// We automatically add in the 'magic' option-variable 'num'.
@@ -245,20 +238,6 @@ const formatNumber = (num: number): string => {
245238
return Intl.NumberFormat(getLocale()).format(num);
246239
};
247240

248-
/*
249-
* Return the ngettext position that matches the given number and lang.
250-
*
251-
* Arguments:
252-
* - num: The number upon which to toggle the plural forms.
253-
* - lang: The language to use as the basis for the pluralization.
254-
*/
255-
export const ngetpos = function (num: number, lang?: Language): number {
256-
const pluralForm = (lang && allPluralForms[lang]) || allPluralForms["en"];
257-
const pos = pluralForm(num);
258-
// Map true to 1 and false to 0, keep any numeric return value the same.
259-
return pos === true ? 1 : pos ? pos : 0;
260-
};
261-
262241
/*
263242
* A dummy identity function. It's used as a signal to automatic
264243
* translation-identification tools that they shouldn't mark this

packages/wonder-blocks-i18n/src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ export {
44
ngettext,
55
doNotTranslate,
66
doNotTranslateYet, // used by handlebars translation functions in webapp
7-
ngetpos,
87
} from "./functions/i18n";
9-
export {loadTranslations, clearTranslations} from "./functions/i18n-store";
8+
export {
9+
loadTranslations,
10+
clearTranslations,
11+
ngetpos,
12+
} from "./functions/i18n-store";
1013

1114
export {localeToFixed, getDecimalSeparator} from "./functions/l10n";
1215
export {getLocale, setLocale} from "./functions/locale";

0 commit comments

Comments
 (0)