Skip to content

Commit 5f52a6c

Browse files
authored
Hotfix Unicode CLDR database (#4404)
* Hotfix Unicode CLDR database * Add entry * Add test
1 parent 6814381 commit 5f52a6c

File tree

6 files changed

+210
-1
lines changed

6 files changed

+210
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2222

2323
## [Unreleased]
2424

25+
### Fixed
26+
27+
- Fixes [#4403](https://github.com/microsoft/BotFramework-WebChat/issues/4403). Patched Unicode CLDR database which caused file upload in Polish to appear blank, by [@compulim](https://github.com/compulim), in PR [#4404](https://github.com/microsoft/BotFramework-WebChat/pull/4404)
28+
2529
### Changed
2630

2731
- Bumped all dependencies to the latest versions, by [@compulim](https://github.com/compulim) in PR [#4392](https://github.com/microsoft/BotFramework-WebChat/pull/4392)
12.9 KB
Loading
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<!DOCTYPE html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<script crossorigin="anonymous" src="/test-harness.js"></script>
6+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
7+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
8+
</head>
9+
<body>
10+
<div id="webchat"></div>
11+
<script>
12+
run(async function () {
13+
const directLine = testHelpers.createDirectLineWithTranscript([
14+
{
15+
type: 'message',
16+
id: 'A0000001',
17+
timestamp: '2022-09-06T20:49:30.3628667Z',
18+
localTimestamp: '2022-09-06T13:49:29.166-07:00',
19+
localTimezone: 'America/Los_Angeles',
20+
channelId: 'directline',
21+
from: {
22+
role: 'user'
23+
},
24+
locale: 'en-US',
25+
attachments: [
26+
{
27+
contentType: 'application/zip',
28+
contentUrl:
29+
'https://docs.botframework.com/static/devportal/client/images/bot-framework-default-placeholder.png',
30+
name: 'some-file.zip'
31+
}
32+
],
33+
channelData: {
34+
attachmentSizes: [1024]
35+
}
36+
}
37+
]);
38+
39+
const store = testHelpers.createStore();
40+
41+
WebChat.renderWebChat(
42+
{
43+
directLine,
44+
locale: 'pl-PL',
45+
store
46+
},
47+
document.getElementById('webchat')
48+
);
49+
50+
await pageConditions.uiConnected();
51+
await host.snapshot();
52+
});
53+
</script>
54+
</body>
55+
</html>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */
2+
3+
describe('upload a file in Polish', () => {
4+
test('should render properly', () =>
5+
runHTML('localization.fileUpload.polish.html'));
6+
});

packages/support/cldr-data/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,15 @@
3535
"files": [
3636
"src/index.js",
3737
"src/install.mjs",
38+
"src/patch.mjs",
3839
"urls.json"
3940
],
4041
"scripts": {
4142
"bump": "npm run bump:prod && npm run bump:dev",
4243
"bump:dev": "npm install $(cat package.json | jq -r '(.devDependencies | keys) - .skipBump | .[]' | awk '{print $1 \"@latest\"}')",
4344
"bump:prod": "npm install $(cat package.json | jq -r '(.dependencies | keys) - .skipBump | .[]' | awk '{print $1 \"@latest\"}')",
4445
"eslint": "npm run precommit",
45-
"install": "node ./src/install.mjs",
46+
"install": "node ./src/install.mjs && node ./src/patch.mjs",
4647
"precommit": "npm run precommit:eslint -- src",
4748
"precommit:eslint": "node ../../../node_modules/eslint/bin/eslint.js --report-unused-disable-directives --max-warnings 0"
4849
},
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { fileURLToPath } from 'url';
2+
import { resolve } from 'path';
3+
import fs from 'fs/promises';
4+
5+
// There is an issue in the Unicode CLDR database (v36):
6+
//
7+
// - Polish has 4 different plural types: "one", "few", "many", "other"
8+
// - However, some units, say "short/digital-kilobyte", only have "other" defined
9+
// - When we localize 1024 (number) into kilobytes, it use the "one" type
10+
// - Since "short/digital-kilobyte/one" is not defined in the database, `globalize` throw exception
11+
//
12+
// In all of our supported languages, we also observed the same issue in Portuguese as well.
13+
//
14+
// As a hotfix, we are patching the Unicode CLDR database for all `[long/short/narrow]/digital-*` rules to make sure it include all plurals needed for that language.
15+
//
16+
// For a long term fix, we should move forward to a newer version of CLDR database, which is outlined in https://github.com/rxaviers/cldr-data-npm/issues/78.
17+
18+
let FORBIDDEN_PROPERTY_NAMES;
19+
20+
function getForbiddenPropertyNames() {
21+
return (
22+
FORBIDDEN_PROPERTY_NAMES ||
23+
(FORBIDDEN_PROPERTY_NAMES = Object.freeze(
24+
Array.from(
25+
new Set([
26+
// As-of writing, `Object.prototype` includes:
27+
// __defineGetter__
28+
// __defineSetter__
29+
// __lookupGetter__
30+
// __lookupSetter
31+
// __proto__
32+
// constructor
33+
// hasOwnProperty
34+
// isPrototypeOf
35+
// propertyIsEnumerable
36+
// toLocaleString
37+
// toString
38+
// valueOf
39+
...Object.getOwnPropertyNames(Object.prototype),
40+
41+
'prototype'
42+
])
43+
)
44+
))
45+
);
46+
}
47+
48+
function isForbiddenPropertyName(propertyName) {
49+
return getForbiddenPropertyNames().includes(propertyName);
50+
}
51+
52+
function toDist(filename) {
53+
if (filename.includes('..')) {
54+
throw new Error('Filename cannot contains "..".');
55+
}
56+
57+
return resolve(fileURLToPath(import.meta.url), '../../dist/', filename);
58+
}
59+
60+
(async function () {
61+
// The function will make sure access to the path is limited.
62+
// eslint-disable-next-line security/detect-non-literal-fs-filename
63+
const plurals = JSON.parse(await fs.readFile(toDist('supplemental/plurals.json'), 'utf8'));
64+
65+
const languagePlurals = new Map();
66+
67+
Object.entries(plurals.supplemental['plurals-type-cardinal']).forEach(([language, pluralsTypeCardinal]) => {
68+
const plurals = ['other'];
69+
70+
languagePlurals.set(language, plurals);
71+
72+
if (!(`pluralRule-count-other` in pluralsTypeCardinal)) {
73+
throw new Error(`Language ${language} does not have plural type "other".`);
74+
}
75+
76+
['zero', 'one', 'two', 'few', 'many'].forEach(pluralType => {
77+
`pluralRule-count-${pluralType}` in pluralsTypeCardinal && plurals.push(pluralType);
78+
});
79+
});
80+
81+
const patchedLanguages = [];
82+
83+
await Promise.all(
84+
Array.from(languagePlurals.entries()).map(async ([language, supportedPluralTypes]) => {
85+
if (!/^[\w-]+$/u.test(language) && isForbiddenPropertyName(language)) {
86+
throw new Error(`Invalid language code "${language}".`);
87+
}
88+
89+
let units;
90+
91+
try {
92+
// The function will make sure access to the path is limited.
93+
// eslint-disable-next-line security/detect-non-literal-fs-filename
94+
units = JSON.parse(await fs.readFile(toDist(`main/${language}/units.json`), 'utf8'));
95+
} catch (err) {
96+
if (err.code === 'ENOENT') {
97+
return;
98+
}
99+
100+
throw err;
101+
}
102+
103+
let numFound = 0;
104+
105+
['long', 'short', 'narrow'].forEach(form => {
106+
// Both "language" and "form" are filtered and free of forbidden values.
107+
// eslint-disable-next-line security/detect-object-injection
108+
Object.entries(units.main[language].units[form]).forEach(([unitName, entry]) => {
109+
if (!unitName.startsWith('digital-')) {
110+
return;
111+
}
112+
113+
if ('unitPattern-count-other' in entry) {
114+
const { 'unitPattern-count-other': other } = entry;
115+
116+
supportedPluralTypes.forEach(pluralType => {
117+
const name = `unitPattern-count-${pluralType}`;
118+
119+
if (!(name in entry)) {
120+
// "name" is free of forbidden values.
121+
// eslint-disable-next-line security/detect-object-injection
122+
entry[name] = other;
123+
numFound++;
124+
}
125+
});
126+
}
127+
});
128+
});
129+
130+
if (numFound) {
131+
patchedLanguages.push(`${language} (${numFound} issues)`);
132+
133+
// The function will make sure access to the path is limited.
134+
// eslint-disable-next-line security/detect-non-literal-fs-filename, no-magic-numbers
135+
await fs.writeFile(toDist(`main/${language}/units.json`), JSON.stringify(units, null, 2));
136+
}
137+
})
138+
);
139+
140+
// We are display output in CLI.
141+
// eslint-disable-next-line no-console
142+
console.log(`Patched ${patchedLanguages.length} languages: ${patchedLanguages.join(', ')}.`);
143+
})();

0 commit comments

Comments
 (0)