Skip to content

Commit 1dfc3e7

Browse files
committed
WIP ui: Use "channel" or "stream" based on server feature level
TODO: - use the right feature level - Make sure servers at that feature level accept `@channel` for wildcard mentions. Discussion: https://chat.zulip.org/#narrow/stream/378-api-design/topic/stream.2Fchannel.20rename.3A.20.40stream.20wildcard.20mention/near/1752545 - write appropriate TODO(server-x) comments - run tools/tx-sync Done at the level of TranslationProvider, which (conveniently) can access the feature level of the active account if any. The messages_en.json changes were done with a handy Perl command that Greg helped me work out: $ perl -i -ne ' print; print if (s/stream/channel/g || s/Stream/Channel/g); ' static/translations/messages_en.json Fixes: #5827
1 parent 69d77de commit 1dfc3e7

File tree

5 files changed

+210
-8
lines changed

5 files changed

+210
-8
lines changed

src/autocomplete/PeopleAutocomplete.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import WildcardMentionItem, {
2121
} from './WildcardMentionItem';
2222
import { TranslationContext } from '../boot/TranslationProvider';
2323
import { getZulipFeatureLevel } from '../account/accountsSelectors';
24+
import { streamChannelRenameFeatureLevel } from '../boot/streamChannelRenamesMap';
2425

2526
type Props = $ReadOnly<{|
2627
filter: string,
@@ -74,6 +75,7 @@ export default function PeopleAutocomplete(props: Props): Node {
7475
destinationNarrow,
7576
// TODO(server-8.0)
7677
zulipFeatureLevel >= 224,
78+
zulipFeatureLevel >= streamChannelRenameFeatureLevel,
7779
_,
7880
);
7981
const filteredUsers = getAutocompleteSuggestion(users, filter, ownUserId, mutedUsers);

src/autocomplete/WildcardMentionItem.js

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import Touchable from '../common/Touchable';
1111
import { createStyleSheet, ThemeContext } from '../styles';
1212
import { caseNarrowDefault, isStreamOrTopicNarrow } from '../utils/narrow';
1313
import { TranslationContext } from '../boot/TranslationProvider';
14+
import { useSelector } from '../react-redux';
15+
import { getZulipFeatureLevel } from '../account/accountsSelectors';
16+
import { streamChannelRenameFeatureLevel } from '../boot/streamChannelRenamesMap';
1417

1518
/**
1619
* A type of wildcard mention recognized by the server.
@@ -38,14 +41,17 @@ export enum WildcardMentionType {
3841
// All of these should appear in messages_en.json so we can make the
3942
// wildcard mentions discoverable in the people autocomplete in the client's
4043
// own language. See getWildcardMentionsForQuery.
41-
const englishCanonicalStringOf = (type: WildcardMentionType): string => {
44+
const englishCanonicalStringOf = (
45+
type: WildcardMentionType,
46+
useChannelTerminology: boolean,
47+
): string => {
4248
switch (type) {
4349
case WildcardMentionType.All:
4450
return 'all';
4551
case WildcardMentionType.Everyone:
4652
return 'everyone';
4753
case WildcardMentionType.Stream:
48-
return 'stream';
54+
return useChannelTerminology ? 'channel' : 'stream';
4955
case WildcardMentionType.Topic:
5056
return 'topic';
5157
}
@@ -86,11 +92,20 @@ export const getWildcardMentionsForQuery = (
8692
query: string,
8793
destinationNarrow: Narrow,
8894
topicMentionSupported: boolean,
95+
useChannelTerminology: boolean,
8996
_: GetText,
9097
): $ReadOnlyArray<WildcardMentionType> => {
9198
const queryMatchesWildcard = (type: WildcardMentionType): boolean =>
92-
typeahead.query_matches_string(query, serverCanonicalStringOf(type), ' ')
93-
|| typeahead.query_matches_string(query, _(englishCanonicalStringOf(type)), ' ');
99+
typeahead.query_matches_string(
100+
query,
101+
serverCanonicalStringOf(type, useChannelTerminology),
102+
' ',
103+
)
104+
|| typeahead.query_matches_string(
105+
query,
106+
_(englishCanonicalStringOf(type, useChannelTerminology)),
107+
' ',
108+
);
94109

95110
const results = [];
96111

@@ -135,9 +150,12 @@ export default function WildcardMentionItem(props: Props): Node {
135150

136151
const _ = useContext(TranslationContext);
137152

153+
const zulipFeatureLevel = useSelector(getZulipFeatureLevel);
154+
const useChannelTerminology = zulipFeatureLevel >= streamChannelRenameFeatureLevel;
155+
138156
const handlePress = useCallback(() => {
139-
onPress(type, serverCanonicalStringOf(type));
140-
}, [onPress, type]);
157+
onPress(type, serverCanonicalStringOf(type, useChannelTerminology));
158+
}, [onPress, type, useChannelTerminology]);
141159

142160
const themeContext = useContext(ThemeContext);
143161

@@ -179,7 +197,7 @@ export default function WildcardMentionItem(props: Props): Node {
179197
<View style={styles.textWrapper}>
180198
<ZulipText
181199
style={styles.text}
182-
text={serverCanonicalStringOf(type)}
200+
text={serverCanonicalStringOf(type, useChannelTerminology)}
183201
numberOfLines={1}
184202
ellipsizeMode="tail"
185203
/>

src/boot/TranslationProvider.js

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ import type { GetText } from '../types';
88
import { useGlobalSelector } from '../react-redux';
99
import { getGlobalSettings } from '../selectors';
1010
import messagesByLanguage from '../i18n/messagesByLanguage';
11+
import { getZulipFeatureLevel, tryGetActiveAccountState } from '../account/accountsSelectors';
12+
import { objectFromEntries } from '../jsBackport';
13+
import { objectEntries } from '../flowPonyfill';
14+
import {
15+
streamChannelRenameFeatureLevel,
16+
streamChannelRenamesMap,
17+
} from './streamChannelRenamesMap';
1118

1219
// $FlowFixMe[incompatible-type] could put a well-typed mock value here, to help write tests
1320
export const TranslationContext: React.Context<GetText> = React.createContext(undefined);
@@ -53,12 +60,58 @@ type Props = $ReadOnly<{|
5360
children: React.Node,
5461
|}>;
5562

63+
/**
64+
* Like messagesByLanguage but with "channel" terminology instead of "stream".
65+
*/
66+
const messagesByLanguageRenamed = objectFromEntries(
67+
objectEntries(messagesByLanguage).map(([language, messages]) => [
68+
language,
69+
objectFromEntries(
70+
objectEntries(messages).map(([messageId, message]) => {
71+
const renamedMessageId = streamChannelRenamesMap[messageId];
72+
if (renamedMessageId == null) {
73+
return [messageId, message];
74+
}
75+
76+
const renamedMessage = messages[renamedMessageId];
77+
if (renamedMessage === renamedMessageId && message !== messageId) {
78+
// The newfangled "channel" string hasn't been translated yet, but
79+
// the older "stream" string has. Consider falling back to that.
80+
if (/^en($|-)/.test(language)) {
81+
// The language is a variety of English. Prefer the newer
82+
// terminology, even though awaiting translation. (Most of our
83+
// strings don't change at all between one English and another.)
84+
return [messageId, renamedMessage];
85+
}
86+
// Use the translation we have, even of the older terminology.
87+
// (In many languages the translations have used an equivalent
88+
// of "channel" all along anyway.)
89+
return [messageId, message];
90+
}
91+
return [messageId, renamedMessage];
92+
}),
93+
),
94+
]),
95+
);
96+
5697
export default function TranslationProvider(props: Props): React.Node {
5798
const { children } = props;
5899
const language = useGlobalSelector(state => getGlobalSettings(state).language);
59100

101+
const activeAccountState = useGlobalSelector(tryGetActiveAccountState);
102+
103+
const effectiveMessagesByLanguage =
104+
activeAccountState == null
105+
|| getZulipFeatureLevel(activeAccountState) > streamChannelRenameFeatureLevel
106+
? messagesByLanguageRenamed
107+
: messagesByLanguage;
108+
60109
return (
61-
<IntlProvider locale={language} textComponent={Text} messages={messagesByLanguage[language]}>
110+
<IntlProvider
111+
locale={language}
112+
textComponent={Text}
113+
messages={effectiveMessagesByLanguage[language]}
114+
>
62115
<TranslationContextTranslator>{children}</TranslationContextTranslator>
63116
</IntlProvider>
64117
);

src/boot/streamChannelRenamesMap.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/* @flow strict-local */
2+
3+
/**
4+
* The feature level at which we want to say "channel" instead of "stream".
5+
*
6+
* Outside a per-account context, check the feature level of the active
7+
* account, if there is one. If there isn't an active account, just choose
8+
* "channel" terminology unconditionally.
9+
*/
10+
export const streamChannelRenameFeatureLevel = 1; // TODO
11+
12+
/**
13+
* A messageId: messageId map, from "stream" terminology to "channel".
14+
*
15+
* When appropriate (see streamChannelRenameFeatureLevel), use this to patch
16+
* UI-string data for all languages, so that the UI says "channel" instead
17+
* of "stream". See https://github.com/zulip/zulip-mobile/issues/5827 .
18+
*
19+
* For example, use this to make a copy of messages_en that has
20+
*
21+
* "Notify stream": "Notify channel",
22+
*
23+
* instead of
24+
*
25+
* "Notify stream": "Notify stream",
26+
* "Notify channel": "Notify channel",
27+
*
28+
* and likewise for all the other languages.
29+
*/
30+
export const streamChannelRenamesMap: {| [string]: string |} = {
31+
stream: 'channel',
32+
'Notify stream': 'Notify channel',
33+
'Who can access the stream?': 'Who can access the channel?',
34+
'Only organization administrators and owners can edit streams.':
35+
'Only organization administrators and owners can edit channels.',
36+
'{realmName} only allows organization administrators or owners to make public streams.':
37+
'{realmName} only allows organization administrators or owners to make public channels.',
38+
'{realmName} only allows organization moderators, administrators, or owners to make public streams.':
39+
'{realmName} only allows organization moderators, administrators, or owners to make public channels.',
40+
'{realmName} only allows full organization members, moderators, administrators, or owners to make public streams.':
41+
'{realmName} only allows full organization members, moderators, administrators, or owners to make public channels.',
42+
'{realmName} only allows organization members, moderators, administrators, or owners to make public streams.':
43+
'{realmName} only allows organization members, moderators, administrators, or owners to make public channels.',
44+
'{realmName} only allows organization administrators or owners to make private streams.':
45+
'{realmName} only allows organization administrators or owners to make private channels.',
46+
'{realmName} only allows organization moderators, administrators, or owners to make private streams.':
47+
'{realmName} only allows organization moderators, administrators, or owners to make private channels.',
48+
'{realmName} only allows full organization members, moderators, administrators, or owners to make private streams.':
49+
'{realmName} only allows full organization members, moderators, administrators, or owners to make private channels.',
50+
'{realmName} only allows organization members, moderators, administrators, or owners to make private streams.':
51+
'{realmName} only allows organization members, moderators, administrators, or owners to make private channels.',
52+
'{realmName} does not allow anybody to make web-public streams.':
53+
'{realmName} does not allow anybody to make web-public channels.',
54+
'{realmName} only allows organization owners to make web-public streams.':
55+
'{realmName} only allows organization owners to make web-public channels.',
56+
'{realmName} only allows organization administrators or owners to make web-public streams.':
57+
'{realmName} only allows organization administrators or owners to make web-public channels.',
58+
'{realmName} only allows organization moderators, administrators, or owners to make web-public streams.':
59+
'{realmName} only allows organization moderators, administrators, or owners to make web-public channels.',
60+
'Cannot subscribe to stream': 'Cannot subscribe to channel',
61+
'Stream #{name} is private.': 'Channel #{name} is private.',
62+
'Please specify a stream.': 'Please specify a channel.',
63+
'Please specify a valid stream.': 'Please specify a valid channel.',
64+
'No messages in stream': 'No messages in channel',
65+
'All streams': 'All channels',
66+
// 'No messages in topic: {streamAndTopic}': 'No messages in topic: {channelAndTopic}',
67+
'Mute stream': 'Mute channel',
68+
'Unmute stream': 'Unmute channel',
69+
'{username} will not be notified unless you subscribe them to this stream.':
70+
'{username} will not be notified unless you subscribe them to this channel.',
71+
'Stream notifications': 'Channel notifications',
72+
'No streams found': 'No channels found',
73+
'Mark stream as read': 'Mark channel as read',
74+
'Failed to mute stream': 'Failed to mute channel',
75+
'Failed to unmute stream': 'Failed to unmute channel',
76+
'Stream settings': 'Channel settings',
77+
'Failed to show stream settings': 'Failed to show channel settings',
78+
'You are not subscribed to this stream': 'You are not subscribed to this channel',
79+
'Create new stream': 'Create new channel',
80+
Stream: 'Channel',
81+
'Edit stream': 'Edit channel',
82+
'Only organization admins are allowed to post to this stream.':
83+
'Only organization admins are allowed to post to this channel.',
84+
'Copy link to stream': 'Copy link to channel',
85+
'Failed to copy stream link': 'Failed to copy channel link',
86+
'A stream with this name already exists.': 'A channel with this name already exists.',
87+
Streams: 'Channels',
88+
};

0 commit comments

Comments
 (0)