Load SAS Emoji translations from @matrix-org/spec (#11429)

* Remove last instance of componentWillMount

* Load SAS Emoji translations from @matrix-org/spec

* Fix import

* Test normalisation on both sides

* update comment for @richvdh

* Delint
This commit is contained in:
Michael Telatynski 2023-08-21 09:15:22 +01:00 committed by GitHub
parent 3d2d08b132
commit d81f71f993
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 147 deletions

View file

@ -174,7 +174,7 @@ An example of a watcher in action would be:
class MyComponent extends React.Component { class MyComponent extends React.Component {
settingWatcherRef = null; settingWatcherRef = null;
componentWillMount() { componentDidMount() {
const callback = (settingName, roomId, level, newValAtLevel, newVal) => { const callback = (settingName, roomId, level, newValAtLevel, newVal) => {
this.setState({ color: newVal }); this.setState({ color: newVal });
}; };

View file

@ -63,6 +63,7 @@
"@matrix-org/analytics-events": "^0.6.0", "@matrix-org/analytics-events": "^0.6.0",
"@matrix-org/matrix-wysiwyg": "^2.4.1", "@matrix-org/matrix-wysiwyg": "^2.4.1",
"@matrix-org/react-sdk-module-api": "^1.0.0", "@matrix-org/react-sdk-module-api": "^1.0.0",
"@matrix-org/spec": "^1.7.0",
"@sentry/browser": "^7.0.0", "@sentry/browser": "^7.0.0",
"@sentry/tracing": "^7.0.0", "@sentry/tracing": "^7.0.0",
"@testing-library/react-hooks": "^8.0.1", "@testing-library/react-hooks": "^8.0.1",

View file

@ -16,9 +16,10 @@ limitations under the License.
import React from "react"; import React from "react";
import { Device } from "matrix-js-sdk/src/matrix"; import { Device } from "matrix-js-sdk/src/matrix";
import { GeneratedSas } from "matrix-js-sdk/src/crypto-api/verification"; import { GeneratedSas, EmojiMapping } from "matrix-js-sdk/src/crypto-api/verification";
import SasEmoji from "@matrix-org/spec/sas-emoji.json";
import { _t, _td } from "../../../languageHandler"; import { _t, getNormalizedLanguageKeys, getUserLanguage } from "../../../languageHandler";
import { PendingActionSpinner } from "../right_panel/EncryptionInfo"; import { PendingActionSpinner } from "../right_panel/EncryptionInfo";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import { fixupColorFonts } from "../../../utils/FontManager"; import { fixupColorFonts } from "../../../utils/FontManager";
@ -42,19 +43,50 @@ interface IState {
cancelling?: boolean; cancelling?: boolean;
} }
/** Convert the names of emojis returned by the js-sdk into the display names, which we use as const SasEmojiMap = new Map<
* a base for our translations. string, // lowercase
{
description: string;
translations: {
[normalizedLanguageKey: string]: string;
};
}
>(
SasEmoji.map(({ description, translated_descriptions: translations }) => [
description.toLowerCase(),
{
description,
// Normalize the translation keys
translations: Object.keys(translations).reduce<Record<string, string>>((o, k) => {
for (const key of getNormalizedLanguageKeys(k)) {
o[key] = translations[k as keyof typeof translations]!;
}
return o;
}, {}),
},
]),
);
/**
* Translate given EmojiMapping into the target locale
* @param mapping - the given EmojiMapping to translate
* @param locale - the BCP 47 locale to translate to, will fall back to English as the base locale for Matrix SAS Emoji.
*/ */
function capFirst(s: string): string { export function tEmoji(mapping: EmojiMapping, locale: string): string {
// Our translations (currently) have names like "Thumbs up". const name = mapping[1];
// const emoji = SasEmojiMap.get(name.toLowerCase());
// With legacy crypto, the js-sdk returns lower-case names ("thumbs up"). With Rust crypto, the js-sdk follows if (!emoji) {
// the spec and returns title-case names ("Thumbs Up"). So, to convert both into names that match our i18n data, console.warn("Emoji not found for translation", name);
// we upcase the first character and downcase the rest. return name;
// }
// Once legacy crypto is dead, we could consider getting rid of this and just making the i18n data use the
// title-case names (which would also match the spec). for (const key of getNormalizedLanguageKeys(locale)) {
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase(); if (!!emoji.translations[key]) {
return emoji.translations[key];
}
}
return emoji.description;
} }
export default class VerificationShowSas extends React.Component<IProps, IState> { export default class VerificationShowSas extends React.Component<IProps, IState> {
@ -64,9 +96,7 @@ export default class VerificationShowSas extends React.Component<IProps, IState>
this.state = { this.state = {
pending: false, pending: false,
}; };
}
public componentWillMount(): void {
// As this component is also used before login (during complete security), // As this component is also used before login (during complete security),
// also make sure we have a working emoji font to display the SAS emojis here. // also make sure we have a working emoji font to display the SAS emojis here.
// This is also done from LoggedInView. // This is also done from LoggedInView.
@ -84,13 +114,15 @@ export default class VerificationShowSas extends React.Component<IProps, IState>
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
const locale = getUserLanguage();
let sasDisplay; let sasDisplay;
let sasCaption; let sasCaption;
if (this.props.sas.emoji) { if (this.props.sas.emoji) {
const emojiBlocks = this.props.sas.emoji.map((emoji, i) => ( const emojiBlocks = this.props.sas.emoji.map((emoji, i) => (
<div className="mx_VerificationShowSas_emojiSas_block" key={i}> <div className="mx_VerificationShowSas_emojiSas_block" key={i}>
<div className="mx_VerificationShowSas_emojiSas_emoji">{emoji[0]}</div> <div className="mx_VerificationShowSas_emojiSas_emoji">{emoji[0]}</div>
<div className="mx_VerificationShowSas_emojiSas_label">{_t(capFirst(emoji[1]))}</div> <div className="mx_VerificationShowSas_emojiSas_label">{tEmoji(emoji, locale)}</div>
</div> </div>
)); ));
sasDisplay = ( sasDisplay = (
@ -171,69 +203,3 @@ export default class VerificationShowSas extends React.Component<IProps, IState>
); );
} }
} }
// List of Emoji strings from the js-sdk, for i18n
_td("Dog");
_td("Cat");
_td("Lion");
_td("Horse");
_td("Unicorn");
_td("Pig");
_td("Elephant");
_td("Rabbit");
_td("Panda");
_td("Rooster");
_td("Penguin");
_td("Turtle");
_td("Fish");
_td("Octopus");
_td("Butterfly");
_td("Flower");
_td("Tree");
_td("Cactus");
_td("Mushroom");
_td("Globe");
_td("Moon");
_td("Cloud");
_td("Fire");
_td("Banana");
_td("Apple");
_td("Strawberry");
_td("Corn");
_td("Pizza");
_td("Cake");
_td("Heart");
_td("Smiley");
_td("Robot");
_td("Hat");
_td("Glasses");
_td("Spanner");
_td("Santa");
_td("Thumbs up");
_td("Umbrella");
_td("Hourglass");
_td("Clock");
_td("Gift");
_td("Light bulb");
_td("Book");
_td("Pencil");
_td("Paperclip");
_td("Scissors");
_td("Lock");
_td("Key");
_td("Hammer");
_td("Telephone");
_td("Flag");
_td("Train");
_td("Bicycle");
_td("Aeroplane");
_td("Rocket");
_td("Trophy");
_td("Ball");
_td("Guitar");
_td("Trumpet");
_td("Bell");
_td("Anchor");
_td("Headphones");
_td("Folder");
_td("Pin");

View file

@ -1221,69 +1221,6 @@
"They don't match": "They don't match", "They don't match": "They don't match",
"They match": "They match", "They match": "They match",
"To be secure, do this in person or use a trusted way to communicate.": "To be secure, do this in person or use a trusted way to communicate.", "To be secure, do this in person or use a trusted way to communicate.": "To be secure, do this in person or use a trusted way to communicate.",
"Dog": "Dog",
"Cat": "Cat",
"Lion": "Lion",
"Horse": "Horse",
"Unicorn": "Unicorn",
"Pig": "Pig",
"Elephant": "Elephant",
"Rabbit": "Rabbit",
"Panda": "Panda",
"Rooster": "Rooster",
"Penguin": "Penguin",
"Turtle": "Turtle",
"Fish": "Fish",
"Octopus": "Octopus",
"Butterfly": "Butterfly",
"Flower": "Flower",
"Tree": "Tree",
"Cactus": "Cactus",
"Mushroom": "Mushroom",
"Globe": "Globe",
"Moon": "Moon",
"Cloud": "Cloud",
"Fire": "Fire",
"Banana": "Banana",
"Apple": "Apple",
"Strawberry": "Strawberry",
"Corn": "Corn",
"Pizza": "Pizza",
"Cake": "Cake",
"Heart": "Heart",
"Smiley": "Smiley",
"Robot": "Robot",
"Hat": "Hat",
"Glasses": "Glasses",
"Spanner": "Spanner",
"Santa": "Santa",
"Thumbs up": "Thumbs up",
"Umbrella": "Umbrella",
"Hourglass": "Hourglass",
"Clock": "Clock",
"Gift": "Gift",
"Light bulb": "Light bulb",
"Book": "Book",
"Pencil": "Pencil",
"Paperclip": "Paperclip",
"Scissors": "Scissors",
"Lock": "Lock",
"Key": "Key",
"Hammer": "Hammer",
"Telephone": "Telephone",
"Flag": "Flag",
"Train": "Train",
"Bicycle": "Bicycle",
"Aeroplane": "Aeroplane",
"Rocket": "Rocket",
"Trophy": "Trophy",
"Ball": "Ball",
"Guitar": "Guitar",
"Trumpet": "Trumpet",
"Bell": "Bell",
"Anchor": "Anchor",
"Headphones": "Headphones",
"Folder": "Folder",
"Welcome": "Welcome", "Welcome": "Welcome",
"Secure messaging for friends and family": "Secure messaging for friends and family", "Secure messaging for friends and family": "Secure messaging for friends and family",
"With free end-to-end encrypted messaging, and unlimited voice and video calls, %(brand)s is a great way to stay in touch.": "With free end-to-end encrypted messaging, and unlimited voice and video calls, %(brand)s is a great way to stay in touch.", "With free end-to-end encrypted messaging, and unlimited voice and video calls, %(brand)s is a great way to stay in touch.": "With free end-to-end encrypted messaging, and unlimited voice and video calls, %(brand)s is a great way to stay in touch.",

View file

@ -0,0 +1,31 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { EmojiMapping } from "matrix-js-sdk/src/crypto-api/verification";
import { tEmoji } from "../../../src/components/views/verification/VerificationShowSas";
describe("tEmoji", () => {
it.each([
["en-GB", "Dog"],
["en", "Dog"],
["de-DE", "Hund"],
["pt", "Cachorro"],
])("should handle locale %s", (locale, expectation) => {
const emoji: EmojiMapping = ["🐶", "Dog"];
expect(tEmoji(emoji, locale)).toEqual(expectation);
});
});

View file

@ -1874,6 +1874,11 @@
dependencies: dependencies:
"@babel/runtime" "^7.17.9" "@babel/runtime" "^7.17.9"
"@matrix-org/spec@^1.7.0":
version "1.7.0"
resolved "https://registry.yarnpkg.com/@matrix-org/spec/-/spec-1.7.0.tgz#8a6b93edf0d99f8a6e0a25eea8613b5ada3e6b56"
integrity sha512-sLRdmk64dNd7X+jXgWFEatJbf2BOFX/a1VxHqWWTerzZntKsjKzz42sD2Mj1QWrsGp01u99fRNU8oy4DcmFn3w==
"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3":
version "2.1.8-no-fsevents.3" version "2.1.8-no-fsevents.3"
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b"