Skip to content

Commit

Permalink
i18n: Sync translations from Transifex
Browse files Browse the repository at this point in the history
Thanks as always to our kind volunteer translators.

i18n: Sync recently-added message strings across languages.

webview build: Spell stdin as `-` for reading rsync filter rules.

On Windows (in Git Bash) there's no /dev/stdin; but this works
instead, as we learned here:
  https://chat.zulip.org/#narrow/stream/48-mobile/topic/issue/near/1047294

(Things still aren't working there as a whole, but we seem to get
past one error and reach another one.)

Conversely, this exact construct `--filter='. -'` appears in an
example in the rsync man page, even at the ancient rsync 2.6.9
that Apple provides on macOS.

Suggested-by: Anders Kaseorg <[email protected]>

README: Migrate Travis badge to travis-ci.com.

Signed-off-by: Anders Kaseorg <[email protected]>

android notif: Correctly stringify pmUsers to fix navigation to group PMs.

Navigation to a group PM on pressing a notification was broken because
pmUsers was incorrectly stringified in GroupPm.getPmUsersString.

E.g., for a group PM among user IDs 13313, 13434, and 13657, it would
stringify to (newline added for readability):

"GroupPm(pmUsers=[13313, 13434, 13657]), GroupPm(pmUsers=[13313,
 13434, 13657]), GroupPm(pmUsers=[13313, 13434, 13657])"

It should instead stringify to "13313, 13434, 13657". (Later in this
series of commits, we remove the space.)

Fix and add a test.

notif tests: Ensure tests pass with representative pm_users values.

To be reverted in the next commit.

In the previous commit, we changed the return value of
GroupPm.getPmUsersString in our Kotlin code from garbage separated
by ', ' to numbers separated by ', '. This commit aims to prove that
', '-separated numbers will be handled correctly, at least as far as
our tests can tell.

But we really want it to be ','-separated (no space), which we do in
the next commit.

notif: Separate ids in pm_users for group PMs with ',' instead of ', '.

', ' would have been handled correctly, but seemingly by accident;
in getNarrowFromNotificationData, pm_users was split on ',' to give
['1', ' 2', ' 3'] (note the spaces), then each element of that array
was converted to a number.

Also, replace the confusing + syntax, as in +idStrs[i], with parseInt.

logging jsdoc: Move "see also" before parameters, to fix parse.

When writing a call to a function that has jsdoc, VS Code shows a
handy popup with the documentation.  It shows first the text for the
parameter you're currently typing, then the text for the function as
a whole.

That popup was showing the "See also" as part of the last parameter's
documentation, rather than that for the function as a whole.  In
particular this means it was only visible when typing the last
parameter.

Fix the jsdoc parse, by moving everything that isn't part of a
parameter's documentation to before the first @param marker.

notif: Normalize realm_uri by parsing it as a URL.

This fixes zulip#4290, a regression in the last release, where trying to
open a notification doesn't actually navigate to the conversation.

The bug is a bit like a revival of zulip#3567: we get the error
"notification realm_uri not found in accounts", and it turns out
that all the accounts have URLs with a trailing slash `/`, while
the `realm_uri` value in the notification doesn't.

On further inspection, it looks like this was introduced when we
started using a URL object for `realm` values, in 865914f.
Previously, since the fix for zulip#3567, we'd stripped trailing slashes
from `realm` values, which were URL strings.  But:
  > new URL('https://example').toString()
  'https://example/'
parsing as a URL object and converting that to a string normalizes
the URL, and one thing that normalization does is *add* a trailing
slash to a URL like our realm URLs (or in general, fill in the path
as `/` if empty.)  When a `realm_uri` with no slash is compared to
one of those, it never matches.

Fix the issue by doubling down on parsing as URL objects.

As a side effect, this normalizes case in the URL's host (and scheme).
We'd previously discussed doing that, at zulip#3671 and here:
  https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/realm.20URL/near/795201
and concluded that parsing as URL objects would be the cleanest way.
We didn't then have an appropriate `URL` implementation handy, but
now we do. :-)

Fixes: zulip#4290

UserItem [nfc]: Take user as one structured object.

This will help us switch from emails to user IDs in downstream
bits of code.

Also adjust several of these call sites to use user IDs for `key`,
rather than emails.

UserItem [nfc]: Pass whole user to callback, rather than email.

This allows UserItem call sites whose callbacks are ready to work in
terms of user IDs to do so without workarounds.  At the same time,
passing whole user objects rather than *just* IDs allows other call
sites to continue to use emails without similar, inverse workarounds.

notif: Always sort user IDs in pm_users.

We already ensure this in the Android case (in FcmMessage.kt);
do so in the iOS case too, and document it in the type.

In practice the list should already have always been sorted: the
server sends it in that form, and has always done so since the
pm_users field was introduced in server commit 1.7.0-2360-g693a9a5e7.
(To see this in the history, try the following Git commands:
   git log -L :get_message_payload:zerver/lib/push_notifications.py
   git log -L :huddle_users:zerver/lib/message.py
.)  So the only way this could have gone wrong is if a rogue server
changed that behavior for some reason; and the main effect of this
commit is really just to document this invariant.

narrow [nfc]: Document more details on identifying group PMs.

Which turned up a couple of bugs!  We'll fix those later in
this series.

example data: Take sender and recipients as pmMessage arguments.

As demonstrated, this allows callers to customize these a lot more
cleanly than they can by overriding the actual message properties
directly.

There are a few call sites we don't update here, in
narrowsReducer-test.js; that file hasn't yet been upgraded to be
well-typed, and so those call sites don't have real User objects
to provide.

example data [nfc]: Use cleaner workaround for Flow "unsealed" issue.

We discovered this nicer one after having used the other one here.
Reminded of the contrast in discussion on other changes in this file:
  zulip#4294 (comment)

types: Make some more indexer-using object types inexact.

I just ran into this issue with CaughtUpState when making another
change.  Apply the workaround there and on the remaining example
in this file, and mark all instances with a conditional TODO.
  • Loading branch information
gnprice authored and abhi0504 committed Nov 24, 2020
1 parent 5fb6057 commit 0e4e322
Show file tree
Hide file tree
Showing 13 changed files with 327 additions and 15 deletions.
17 changes: 6 additions & 11 deletions src/__tests__/lib/exampleData.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,14 +290,13 @@ const randMessageId: () => number = makeUniqueRandInt('message ID', 10000000);
* Beware! These values may not be representative.
*/
export const pmMessage = (args?: {|
...$Rest<Message, {}>,
sender?: User,
recipients?: User[],
...$Rest<Message, { }>,
sender ?: User,
recipients ?: User[],
|}): Message => {
// The `Object.freeze` is to work around a Flow issue:
// https://github.com/facebook/flow/issues/2386#issuecomment-695064325
const { sender = otherUser, recipients = [otherUser, selfUser], ...extra } =
args ?? Object.freeze({});
const { sender = otherUser, recipients = [selfUser], ...extra } = args ?? Object.freeze({});

const baseMessage: Message = {
...messagePropertiesBase,
Expand Down Expand Up @@ -331,14 +330,10 @@ const messagePropertiesFromStream = (stream1: Stream) => {
*
* Beware! These values may not be representative.
*/
export const streamMessage = (args?: {|
...$Rest<Message, {}>,
stream?: Stream,
sender?: User,
|}): Message => {
export const streamMessage = (args?: {| ...$Rest<Message, { }>, stream ?: Stream |}): Message => {
// The `Object.freeze` is to work around a Flow issue:
// https://github.com/facebook/flow/issues/2386#issuecomment-695064325
const { stream: streamInner = stream, sender = otherUser, ...extra } = args ?? Object.freeze({});
const { stream: streamInner = stream, ...extra } = args ?? Object.freeze({});

const baseMessage: Message = {
...messagePropertiesBase,
Expand Down
8 changes: 4 additions & 4 deletions src/chat/GroupDetailsScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ type Props = $ReadOnly<{|
// don't invoke it without type-checking anywhere else (in fact, we
// don't invoke it anywhere else at all), we know it gets the
// `navigation` prop for free, with the stack-nav shape.
navigation: NavigationStackProp<{|
navigation: NavigationStackProp < {|
...NavigationStateRoute,
params: {| recipients: UserOrBot[] |},
params: {| recipients: UserOrBot[] |},
|}>,

dispatch: Dispatch,
|}>;

class GroupDetailsScreen extends PureComponent<Props> {
handlePress = (user: UserOrBot) => {
NavigationService.dispatch(navigateToAccountDetails(user.user_id));
this.props.dispatch(navigateToAccountDetails(user.user_id));
};

render() {
Expand All @@ -45,4 +45,4 @@ class GroupDetailsScreen extends PureComponent<Props> {
}
}

export default connect<{||}, _, _>()(GroupDetailsScreen);
export default connect < {||}, _, _ > ()(GroupDetailsScreen);
30 changes: 30 additions & 0 deletions src/notification/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ import {
} from './notificationActions';
import { identityOfAuth } from '../account/accountMisc';
import { fromAPNs } from './extract';
<<<<<<< HEAD
import { tryParseUrl } from '../utils/url';
import { pmKeyRecipientsFromIds } from '../utils/recipient';
||||||| parent of 84d50e52 (i18n: Sync translations from Transifex)
=======
import { tryParseUrl } from '../utils/url';
>>>>>>> 84d50e52 (i18n: Sync translations from Transifex)

/**
* Identify the account the notification is for, if possible.
Expand Down Expand Up @@ -105,9 +110,34 @@ export const getNarrowFromNotificationData = (
return privateNarrow(data.sender_email);
}

<<<<<<< HEAD
const ids = data.pm_users.split(',').map(s => parseInt(s, 10));
const users = pmKeyRecipientsFromIds(ids, usersById, ownUserId);
return users && groupNarrow(users.map(u => u.email));
||||||| parent of 84d50e52 (i18n: Sync translations from Transifex)
const emails = [];
const idStrs = data.pm_users.split(',');
for (let i = 0; i < idStrs.length; ++i) {
const user = usersById.get(+idStrs[i]);
if (!user) {
return null;
}
emails.push(user.email);
}
return groupNarrow(emails);
=======
const emails = [];
const idStrs = data.pm_users.split(',');
for (let i = 0; i < idStrs.length; ++i) {
const id = parseInt(idStrs[i], 10);
const user = usersById.get(id);
if (!user) {
return null;
}
emails.push(user.email);
}
return groupNarrow(emails);
>>>>>>> 84d50e52 (i18n: Sync translations from Transifex)
};

const getInitialNotification = async (): Promise<Notification | null> => {
Expand Down
10 changes: 10 additions & 0 deletions src/reactions/ReactionUserList.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,18 @@ type Props = $ReadOnly<{|
* Used within `MessageReactionList`.
*/
class ReactionUserList extends PureComponent<Props> {
<<<<<<< HEAD
handlePress = (user: UserOrBot) => {
NavigationService.dispatch(navigateToAccountDetails(user.user_id));
||||||| parent of 84d50e52 (i18n: Sync translations from Transifex)
handlePress = (userId: number) => {
const { dispatch } = this.props;
dispatch(navigateToAccountDetails(userId));
=======
handlePress = (user: UserOrBot) => {
const { dispatch } = this.props;
dispatch(navigateToAccountDetails(user.user_id));
>>>>>>> 84d50e52 (i18n: Sync translations from Transifex)
};

render() {
Expand Down
14 changes: 14 additions & 0 deletions src/users/UsersCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@

import React, { PureComponent } from 'react';

<<<<<<< HEAD
import NavigationService from '../nav/NavigationService';
import type { Dispatch, PresenceState, User, UserOrBot } from '../types';
||||||| parent of 84d50e52 (i18n: Sync translations from Transifex)
import type { Dispatch, PresenceState, User } from '../types';
=======
import type { Dispatch, PresenceState, User, UserOrBot } from '../types';
>>>>>>> 84d50e52 (i18n: Sync translations from Transifex)
import { connect } from '../react-redux';
import { privateNarrow } from '../utils/narrow';
import UserList from './UserList';
Expand All @@ -20,8 +26,16 @@ type Props = $ReadOnly<{|
class UsersCard extends PureComponent<Props> {
handleUserNarrow = (user: UserOrBot) => {
const { dispatch } = this.props;
<<<<<<< HEAD
NavigationService.dispatch(navigateBack());
dispatch(doNarrow(privateNarrow(user.email)));
||||||| parent of 84d50e52 (i18n: Sync translations from Transifex)
dispatch(navigateBack());
dispatch(doNarrow(privateNarrow(email)));
=======
dispatch(navigateBack());
dispatch(doNarrow(privateNarrow(user.email)));
>>>>>>> 84d50e52 (i18n: Sync translations from Transifex)
};

render() {
Expand Down
17 changes: 17 additions & 0 deletions src/utils/internalLinks.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,26 @@ export const getNarrowFromLink = (

switch (type) {
case 'pm': {
<<<<<<< HEAD
const ids = parsePmOperand(paths[1], usersById, ownUserId);
const users = pmKeyRecipientsFromIds(ids, usersById, ownUserId);
return users && groupNarrow(users.map(u => u.email));
||||||| parent of 84d50e52 (i18n: Sync translations from Transifex)
const recipientEmails = parsePmOperand(paths[1], usersById);
if (recipientEmails === null) {
return null;
}
return groupNarrow(recipientEmails);
=======
const recipientEmails = parsePmOperand(paths[1], usersById);
if (recipientEmails === null) {
return null;
}
// BUG: should normalize recipients; see comment on groupNarrow.
// (We're parsing a link someone wrote in a message, so the server
// gives us no guarantees here.)
return groupNarrow(recipientEmails);
>>>>>>> 84d50e52 (i18n: Sync translations from Transifex)
}
case 'topic':
return topicNarrow(parseStreamOperand(paths[1], streamsById), parseTopicOperand(paths[3]));
Expand Down
50 changes: 50 additions & 0 deletions src/utils/narrow.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const privateNarrow = (email: string): Narrow => [
},
];

<<<<<<< HEAD
/**
* A group PM narrow.
*
Expand Down Expand Up @@ -61,6 +62,55 @@ export const privateNarrow = (email: string): Narrow => [
// notification's pm_users, which is sorted.
// * Good: messageHeaderAsHtml: comes from pmKeyRecipientsFromMessage,
// which filters and sorts by ID
||||||| parent of 84d50e52 (i18n: Sync translations from Transifex)
=======
/**
* A group PM narrow.
*
* The users represented in `emails` should agree, as a (multi)set, with
* `pmKeyRecipientsFromMessage`.
*
* They might not have a consistent sorting. (This would be good to fix.)
* Consumers of this data should sort for themselves when making comparisons.
*/
// Ideally, all callers should agree on how they're sorted, too. Because
// they don't, we have latent bugs (possibly a live one somewhere) where we
// can wind up with several distinct narrows that are actually the same
// group PM conversation.
//
// For example this happens if you have a group PM conversation where email
// and ID sorting don't happen to coincide; visit a group PM conversation
// from the main nav (either the unreads or PMs screen) -- which sorts by
// email; and then visit the same conversation from a recipient bar on the
// "all messages" narrow -- which sorts by ID. The Redux logs in the
// debugger will show two different entries in `state.narrows`. This bug is
// merely latent only because it doesn't (as far as we know) have any
// user-visible effect.
//
// But we also have some callers that don't even ensure the set is the right
// one, with the self-user properly there or not. Known call stacks:
// * BUG #4293: getNarrowFromNotificationData: comes from notification's
// pm_users... which is sorted but not filtered. This means if you
// follow a group PM notif, then get another message in that
// conversation, it won't appear. (And if you send, it'll promptly
// disappear.)
// * BUG, ish: getNarrowFromLink doesn't ensure this precondition is met.
// And... there's basically a bug in the webapp, where the URL that
// appears in the location bar for a group PM conversation excludes
// self -- so it's unusable if you try to give someone else in it a
// link to a particular message, say. But conversely I guess it means
// that the mobile app actually works just as well as the webapp on the
// links people generate from the webapp.
// * OK, perilously, unsorted: CreateGroupScreen: the self user isn't
// offered in the UI, so effectively the list is filtered; can call
// with just one email, but happily this works out the same as pmNarrow
// * OK, email: PmConversationList < PmConversationCard: the data comes
// from `getRecentConversations`, which filters and sorts by email
// * OK, email: PmConversationList < UnreadCards: ditto
// * OK, unsorted: getNarrowFromMessage
// * Good: messageHeaderAsHtml: comes from pmKeyRecipientsFromMessage,
// which filters and sorts by ID
>>>>>>> 84d50e52 (i18n: Sync translations from Transifex)
export const groupNarrow = (emails: string[]): Narrow => [
{
operator: 'pm-with',
Expand Down
25 changes: 25 additions & 0 deletions static/translations/messages_de.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@
"Username": "Benutzername",
"Password": "Passwort",
"Why not start the conversation?": "Wieso beginnst Du nicht einfach die Unterhaltung?",
<<<<<<< HEAD
"That conversation doesn't seem to exist.": "Diese Unterhaltung scheint nicht zu existieren.",
||||||| parent of 84d50e52 (i18n: Sync translations from Transifex)
=======
"That conversation doesn't seem to exist.": "That conversation doesn't seem to exist.",
>>>>>>> 84d50e52 (i18n: Sync translations from Transifex)
"Chat": "Chat",
"Sign in with {method}": "Anmelden mit {method}",
"Cannot connect to server": "Verbindung zum Server kann nicht hergestellt werden",
Expand All @@ -48,10 +53,22 @@
"Add a reaction": "Reaktion hinzufügen",
"Copy to clipboard": "In die Ablage kopieren",
"Link copied to clipboard": "Link in die Ablage kopiert",
<<<<<<< HEAD
"This time is in your timezone. Original text was “{originalText}”.": "Diese Zeitangabe entspricht deiner Zeitzone. Der Originaltext war “{originalText}”.",
||||||| parent of 84d50e52 (i18n: Sync translations from Transifex)
"This time is in your timezone. Original text was '{originalText}'.": "Diese Zeit entspricht Ihrer Zeitzone. Der Originaltext lautet '{originalText}'.",
=======
"This time is in your timezone. Original text was “{originalText}”.": "This time is in your timezone. Original text was “{originalText}”.",
>>>>>>> 84d50e52 (i18n: Sync translations from Transifex)
"Mute topic": "Thema stummschalten",
"Delete topic": "Thema löschen",
<<<<<<< HEAD
"Are you sure you want to delete the topic “{topic}”?": "Bist du sicher, dass du das Thema “{topic}” löschen möchtest?",
||||||| parent of 84d50e52 (i18n: Sync translations from Transifex)
"Are you sure you want to delete the topic '{topic}'?": "Bist du sicher, dass du das Thema '{topic}' löschen möchtest?",
=======
"Are you sure you want to delete the topic “{topic}”?": "Are you sure you want to delete the topic “{topic}”?",
>>>>>>> 84d50e52 (i18n: Sync translations from Transifex)
"This will also delete all messages in the topic.": "Dadurch werden auch alle Nachrichten mit diesem Thema gelöscht.",
"Unmute topic": "Stummschaltung des Themas aufheben",
"Mute stream": "Stream stummschalten",
Expand Down Expand Up @@ -215,8 +232,16 @@
"Sending Message...": "Sende Nachricht...",
"Failed to send message": "Konnte die Nachricht nicht senden",
"Message sent": "Nachricht gesendet",
<<<<<<< HEAD
"Couldn’t load information about {fullName}": "Konnte keine Informationen über {fullName} laden",
"What’s your status?": "Was ist dein Status?",
||||||| parent of 84d50e52 (i18n: Sync translations from Transifex)
"Couldn't load information about {fullName}": "Konnte keine Informationen über {fullName} laden",
"What's your status?": "Was ist dein Status?",
=======
"Couldn’t load information about {fullName}": "Couldn’t load information about {fullName}",
"What’s your status?": "What’s your status?",
>>>>>>> 84d50e52 (i18n: Sync translations from Transifex)
"Click to join video call": "Hier klicken, um Videoanruf beizutreten",
"📅 In a meeting": "📅 Bei einem Treffen",
"🚌 Commuting": "🚌 Unterwegs",
Expand Down
47 changes: 47 additions & 0 deletions static/translations/messages_fi.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,15 @@
"Username": "Käyttäjätunnus",
"Password": "Salasana",
"Why not start the conversation?": "Miksi et aloittaisi keskustelua?",
<<<<<<< HEAD
"That conversation doesn't seem to exist.": "Keskustelua ei löydy.",
"Chat": "Chat",
||||||| parent of 84d50e52 (i18n: Sync translations from Transifex)
"Chat": "Chatti",
=======
"That conversation doesn't seem to exist.": "That conversation doesn't seem to exist.",
"Chat": "Chatti",
>>>>>>> 84d50e52 (i18n: Sync translations from Transifex)
"Sign in with {method}": "Sign in with {method}",
"Cannot connect to server": "Palvelimeen ei saada yhteyttä",
"Please enter a valid URL": "Tarkista URL",
Expand All @@ -51,9 +58,19 @@
"This time is in your timezone. Original text was “{originalText}”.": "This time is in your timezone. Original text was “{originalText}”.",
"Mute topic": "Mykistä aihe",
"Delete topic": "Poista aihe",
<<<<<<< HEAD
"Are you sure you want to delete the topic “{topic}”?": "Oletko varma että haluat poistaa aiheen \"{topic}\"?",
"This will also delete all messages in the topic.": "Tämä poistaa myös aiheen kaikki viestit",
"Unmute topic": "Peru aiheen mykistys",
||||||| parent of 84d50e52 (i18n: Sync translations from Transifex)
"Are you sure you want to delete the topic '{topic}'?": "Are you sure you want to delete the topic '{topic}'?",
"This will also delete all messages in the topic.": "This will also delete all messages in the topic.",
"Unmute topic": "Poista aiheen mykistys",
=======
"Are you sure you want to delete the topic “{topic}”?": "Are you sure you want to delete the topic “{topic}”?",
"This will also delete all messages in the topic.": "Tämä poistaa myös aiheen kaikki viestit",
"Unmute topic": "Peru aiheen mykistys",
>>>>>>> 84d50e52 (i18n: Sync translations from Transifex)
"Mute stream": "Mykistä kanava",
"Unmute stream": "Peru kanavan mykistys",
"No Internet connection": "Ei Internet yhteyttä",
Expand Down Expand Up @@ -210,6 +227,7 @@
"Muted": "Mykistetty",
"No topics found": "Aiheita ei löydy",
"Share on Zulip": "Share on Zulip",
<<<<<<< HEAD
"Choose recipients": "Valitse vastaanottajat",
"Please choose recipients to share with.": "Valitse kenelle jaetaan.",
"Sending Message...": "Viestiä lähetetään",
Expand All @@ -223,4 +241,33 @@
"🤒 Out sick": "🤒 Sairaslomalla",
"🌴 Vacationing": "🌴 Lomalla",
"🏠 Working remotely": "🏠 Etätöissä"
||||||| parent of 84d50e52 (i18n: Sync translations from Transifex)
"Choose recipients": "Choose recipients",
"Please choose recipients to share with.": "Please choose recipients to share with.",
"Sending Message...": "Sending Message...",
"Failed to send message": "Failed to send message",
"Message sent": "Message sent",
"Couldn't load information about {fullName}": "Couldn't load information about {fullName}",
"What's your status?": "What's your status?",
"Click to join video call": "Click to join video call",
"📅 In a meeting": "📅 In a meeting",
"🚌 Commuting": "🚌 Commuting",
"🤒 Out sick": "🤒 Out sick",
"🌴 Vacationing": "🌴 Vacationing",
"🏠 Working remotely": "🏠 Working remotely"
=======
"Choose recipients": "Valitse vastaanottajat",
"Please choose recipients to share with.": "Valitse kenelle jaetaan.",
"Sending Message...": "Viestiä lähetetään",
"Failed to send message": "Viestin lähetys epäonnistui",
"Message sent": "Viesti lähetetty",
"Couldn’t load information about {fullName}": "Couldn’t load information about {fullName}",
"What’s your status?": "What’s your status?",
"Click to join video call": "Klikkaa liittyäksesi videopuheluun",
"📅 In a meeting": "📅 Kokouksessa",
"🚌 Commuting": "🚌 Matkalla",
"🤒 Out sick": "🤒 Sairaslomalla",
"🌴 Vacationing": "🌴 Lomalla",
"🏠 Working remotely": "🏠 Etätöissä"
>>>>>>> 84d50e52 (i18n: Sync translations from Transifex)
}
Loading

0 comments on commit 0e4e322

Please sign in to comment.