Skip to content

Commit 6e6d186

Browse files
authored
Localization for composites (Azure#595)
* renamed and exported locales. updated localization docs. * comment update * Change files * fixed imports after rename * Change files * updated api.md files * fixed locale options in storybook. fixed CustomLocale.snippet. * addressed PR comments * wording updated * added errorBar strings in french * exposed props for locale and wrapped composites internally with LocalizationProvider. * updated fr_FR * Composite localization doc added * moved composite localization in Localization resource * Change files * moved localized composites snippet * documentation and composite refactors * moved localized composites snippet back * @DefaultValue used in prop comments
1 parent 21ae909 commit 6e6d186

File tree

8 files changed

+141
-20
lines changed

8 files changed

+141
-20
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Exposed locale prop to localize composites.",
4+
"packageName": "@internal/react-composites",
5+
"email": "miguelgamis@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

packages/communication-react/review/communication-react.api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ export type CallCompositePage = 'configuration' | 'call' | 'error' | 'errorJoini
335335
export type CallCompositeProps = {
336336
adapter: CallAdapter;
337337
fluentTheme?: PartialTheme | Theme;
338+
locale?: Locale;
338339
callInvitationURL?: string;
339340
onRenderAvatar?: (props: PlaceholderProps, defaultOnRender: (props: PlaceholderProps) => JSX.Element) => JSX.Element;
340341
identifiers?: Identifiers;
@@ -538,6 +539,7 @@ export type ChatCompositeClientState = {
538539
export type ChatCompositeProps = {
539540
adapter: ChatAdapter;
540541
fluentTheme?: PartialTheme | Theme;
542+
locale?: Locale;
541543
onRenderAvatar?: (userId: string, avatarType?: 'chatThread' | 'participantList') => JSX.Element;
542544
onRenderMessage?: (messageProps: MessageProps, defaultOnRender?: DefaultMessageRendererType) => JSX.Element;
543545
onRenderTypingIndicator?: (typingUsers: CommunicationParticipant[]) => JSX.Element;

packages/react-composites/review/react-composites.api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { DeviceManagerState } from '@internal/calling-stateful-client';
2323
import type { ErrorType } from '@internal/react-components';
2424
import { GroupCallLocator } from '@azure/communication-calling';
2525
import { Identifiers } from '@internal/react-components';
26+
import { Locale } from '@internal/react-components';
2627
import { MessageProps } from '@internal/react-components';
2728
import type { MicrosoftTeamsUserKind } from '@azure/communication-common';
2829
import { PartialTheme } from '@fluentui/react';
@@ -250,6 +251,7 @@ export type CallCompositePage = 'configuration' | 'call' | 'error' | 'errorJoini
250251
export type CallCompositeProps = {
251252
adapter: CallAdapter;
252253
fluentTheme?: PartialTheme | Theme;
254+
locale?: Locale;
253255
callInvitationURL?: string;
254256
onRenderAvatar?: (props: PlaceholderProps, defaultOnRender: (props: PlaceholderProps) => JSX.Element) => JSX.Element;
255257
identifiers?: Identifiers;
@@ -343,6 +345,7 @@ export type ChatCompositeClientState = {
343345
export type ChatCompositeProps = {
344346
adapter: ChatAdapter;
345347
fluentTheme?: PartialTheme | Theme;
348+
locale?: Locale;
346349
onRenderAvatar?: (userId: string, avatarType?: 'chatThread' | 'participantList') => JSX.Element;
347350
onRenderMessage?: (messageProps: MessageProps, defaultOnRender?: DefaultMessageRendererType) => JSX.Element;
348351
onRenderTypingIndicator?: (typingUsers: CommunicationParticipant[]) => JSX.Element;

packages/react-composites/src/composites/CallComposite/Call.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,22 @@ import { CallAdapter, CallCompositePage } from './adapter/CallAdapter';
1111
import { IdentifierProvider, Identifiers, PlaceholderProps } from '@internal/react-components';
1212
import { useSelector } from './hooks/useSelector';
1313
import { getPage } from './selectors/baseSelectors';
14-
import { FluentThemeProvider } from '@internal/react-components';
14+
import { FluentThemeProvider, LocalizationProvider, Locale } from '@internal/react-components';
1515

1616
export type CallCompositeProps = {
1717
adapter: CallAdapter;
1818
/**
1919
* Fluent theme for the composite.
2020
*
21-
* Defaults to a light theme if undefined.
21+
* @defaultValue `light theme`
2222
*/
2323
fluentTheme?: PartialTheme | Theme;
24+
/**
25+
* Locale for the composite.
26+
*
27+
* @defaultValue `English (US)`
28+
*/
29+
locale?: Locale;
2430
callInvitationURL?: string;
2531
onRenderAvatar?: (props: PlaceholderProps, defaultOnRender: (props: PlaceholderProps) => JSX.Element) => JSX.Element;
2632
identifiers?: Identifiers;
@@ -93,7 +99,7 @@ interface CallInternalProps extends CallCompositeProps {
9399
* @internal
94100
*/
95101
export const CallCompositeInternal = (props: CallInternalProps): JSX.Element => {
96-
const { adapter, callInvitationURL, fluentTheme, identifiers } = props;
102+
const { adapter, callInvitationURL, fluentTheme, locale, identifiers } = props;
97103

98104
useEffect(() => {
99105
(async () => {
@@ -104,7 +110,7 @@ export const CallCompositeInternal = (props: CallInternalProps): JSX.Element =>
104110
})();
105111
}, [adapter]);
106112

107-
return (
113+
const callElement = (
108114
<FluentThemeProvider fluentTheme={fluentTheme}>
109115
<IdentifierProvider identifiers={identifiers}>
110116
<CallAdapterProvider adapter={adapter}>
@@ -117,4 +123,6 @@ export const CallCompositeInternal = (props: CallInternalProps): JSX.Element =>
117123
</IdentifierProvider>
118124
</FluentThemeProvider>
119125
);
126+
127+
return locale ? LocalizationProvider({ locale, children: callElement }) : callElement;
120128
};

packages/react-composites/src/composites/ChatComposite/ChatComposite.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,27 @@ import {
1010
CommunicationParticipant,
1111
DefaultMessageRendererType,
1212
FluentThemeProvider,
13+
LocalizationProvider,
1314
MessageProps,
1415
IdentifierProvider,
15-
Identifiers
16+
Identifiers,
17+
Locale
1618
} from '@internal/react-components';
1719

1820
export type ChatCompositeProps = {
1921
adapter: ChatAdapter;
2022
/**
2123
* Fluent theme for the composite.
2224
*
23-
* Defaults to a light theme if undefined.
25+
* @defaultValue `light theme`
2426
*/
2527
fluentTheme?: PartialTheme | Theme;
28+
/**
29+
* Locale for the composite.
30+
*
31+
* @defaultValue `English (US)`
32+
*/
33+
locale?: Locale;
2634
onRenderAvatar?: (userId: string, avatarType?: 'chatThread' | 'participantList') => JSX.Element;
2735
onRenderMessage?: (messageProps: MessageProps, defaultOnRender?: DefaultMessageRendererType) => JSX.Element;
2836
onRenderTypingIndicator?: (typingUsers: CommunicationParticipant[]) => JSX.Element;
@@ -56,10 +64,18 @@ export type ChatOptions = {
5664
};
5765

5866
export const ChatComposite = (props: ChatCompositeProps): JSX.Element => {
59-
const { adapter, fluentTheme, options, identifiers, onRenderAvatar, onRenderTypingIndicator, onRenderMessage } =
60-
props;
67+
const {
68+
adapter,
69+
fluentTheme,
70+
locale,
71+
options,
72+
identifiers,
73+
onRenderAvatar,
74+
onRenderTypingIndicator,
75+
onRenderMessage
76+
} = props;
6177

62-
return (
78+
const chatElement = (
6379
<FluentThemeProvider fluentTheme={fluentTheme}>
6480
<IdentifierProvider identifiers={identifiers}>
6581
<ChatAdapterProvider adapter={adapter}>
@@ -76,4 +92,6 @@ export const ChatComposite = (props: ChatCompositeProps): JSX.Element => {
7692
</IdentifierProvider>
7793
</FluentThemeProvider>
7894
);
95+
96+
return locale ? LocalizationProvider({ locale, children: chatElement }) : chatElement;
7997
};

packages/storybook/stories/Localization.stories.mdx

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import TruncationSnippetText from '!!raw-loader!./snippets/localization/Truncati
1212
import { TruncationSnippet } from './snippets/localization/Truncation.snippet';
1313
import SetRTLSnippetText from '!!raw-loader!./snippets/localization/SetRTL.snippet';
1414
import { SetRTLForCanvasSnippet } from './snippets/localization/SetRTLForCanvas.snippet';
15+
import LocalizedComposites from '!!raw-loader!./snippets/localization/LocalizedComposites.snippet.tsx';
1516

1617
<Meta id="localization" title="Localization" />
1718

@@ -23,22 +24,28 @@ Developers can provide their own localization files to be used for the UI Librar
2324

2425
## LocalizationProvider
2526

26-
`LocalizationProvider` is a component wrapper that sets all the strings for ACS components using a `Locale`. By default, all
27+
`LocalizationProvider` is a component wrapper that sets all the strings for UI Library components using a `Locale`. By default, all
2728
components use our English (US) locale. If desired, LocalizationProvider can be used to set a different locale. Out of the box,
2829
the UI library includes a set of locales usable with the UI components and composites
2930

3031
### Usage
3132

32-
To use the LocalizationProvider, import it from `@azure/communication-react` and wrap it around ACS UI SDK components,
33-
For the example below, we will localize buttons in a `ControlBar` in French (France) by also importing locale `fr_FR` from
34-
`@azure/communication-react`.
33+
To use the LocalizationProvider, import it from `@azure/communication-react` and wrap it around UI Library components. Then assign a `Locale` to the `locale` prop.
34+
For the example below, we will localize buttons in a `ControlBar` in French (France) by also importing locale `fr_FR` from `@azure/communication-react`.
3535

3636
<Source code={LocalizationProviderSnippetText} />
3737

3838
<Canvas withSource="none">
3939
<LocalizationProviderSnippet />
4040
</Canvas>
4141

42+
### Composites
43+
44+
If you want to localize your composite, assign a `Locale` to the `locale` prop of the composite. You may import one of our locales like `fr_FR` for French (France)
45+
in the example below.
46+
47+
<Source code={LocalizedComposites} />
48+
4249
## Overriding locales
4350

4451
There are two options to customize the locales that we provide. For the examples below, let's say you wish to have a `ControlBar`
@@ -56,14 +63,14 @@ value `strings.cameraButton.offLabel` like shown below.
5663
</Canvas>
5764

5865
`Locale` stores all strings in the `strings` property as a nested object. The component name is the first identifer and the string key is
59-
the second identifier such that each string can be accessed in this pattern `strings.<componentName>.<stringKey>`. If you are using typescript and
60-
have intellisense available in your IDE, it will be easy to discover all the strings.
66+
the second identifier such that each string can be accessed in this pattern `strings.<componentName>.<stringKey>`. If you are using
67+
typescript and have intellisense available in your IDE, it will be easy to discover all the strings.
6168

6269
### Option 2: Override `strings` prop in the UI Component
6370

64-
The second option is to pass custom strings directly through the `strings` prop. Each component in ACS has an optional `strings`
65-
property that interfaces all strings that can be overriden in that component. There is no need to use `LocalizationProvider` because all components default
66-
to English (US). Just assign the string directly through `CameraButton` like shown below.
71+
The second option is to pass custom strings directly through the `strings` prop. Strings can be overriden for a UI Library
72+
component through the optional `strings` property. There is no need to use `LocalizationProvider` because all
73+
components default to English (US). Just assign the string directly through `CameraButton` like shown below.
6774

6875
<Source code={StringsPropertySnippetText} />
6976

@@ -92,14 +99,14 @@ We suggest the following options to achieve a better looking interface.
9299
<TruncationSnippet />
93100
</Canvas>
94101

95-
Note: All ACS UI SDK components will have aria labels with the full text as part of our Accessibility promise.
102+
Note: All UI Library components will have aria labels with the full text as part of our Accessibility promise.
96103

97104
3. Using styling to reduce font size.
98105

99106
## Setting RTL
100107

101108
To set rtl in your web application, first import and call `setRTL` from `@fluentui/react` to set the root element's `dir` property to "rtl".
102-
All Azure Communication Services UI library components will be "flipped" just like the example `ControlBar` in Arabic below.
109+
All UI library components will be "flipped" just like the example `ControlBar` in Arabic below.
103110

104111
<Source code={SetRTLSnippetText} />
105112

packages/storybook/stories/QuickStarts/QuickstartComposite.stories.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,4 @@ For more information, see the following resources:
128128
- [UI Library Use Cases](/?path=/story/use-cases--page)
129129
- [UI Library Styling](/?path=/story/styling--page)
130130
- [UI Library Theming](/?path=/story/theming--page)
131+
- [UI Library Localization](/?path=/story/localization--page#composites)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { AzureCommunicationTokenCredential } from '@azure/communication-common';
2+
import {
3+
CallComposite,
4+
CallAdapter,
5+
createAzureCommunicationCallAdapter,
6+
ChatComposite,
7+
ChatAdapter,
8+
createAzureCommunicationChatAdapter,
9+
fr_FR
10+
} from '@azure/communication-react';
11+
import React, { useEffect, useMemo, useState } from 'react';
12+
13+
function App(): JSX.Element {
14+
const endpointUrl = '<Azure Communication Services Resource Endpoint>';
15+
const userId = '<Azure Communication Services Identifier>';
16+
const displayName = '<Display Name>';
17+
const token = '<Azure Communication Services Access Token>';
18+
19+
//Calling Variables
20+
//For Group Id, developers can pass any GUID they can generate
21+
const groupId = '<Developer generated GUID>';
22+
const [callAdapter, setCallAdapter] = useState<CallAdapter>();
23+
24+
//Chat Variables
25+
const threadId = '<Get thread id from chat service>';
26+
const [chatAdapter, setChatAdapter] = useState<ChatAdapter>();
27+
28+
// We can't even initialize the Chat and Call adapters without a well-formed token.
29+
const credential = useMemo(() => {
30+
try {
31+
return new AzureCommunicationTokenCredential(token);
32+
} catch {
33+
console.error('Failed to construct token credential');
34+
return undefined;
35+
}
36+
}, [token]);
37+
38+
useEffect(() => {
39+
const createAdapter = async (): Promise<void> => {
40+
setChatAdapter(
41+
await createAzureCommunicationChatAdapter(
42+
endpointUrl,
43+
{ kind: 'communicationUser', communicationUserId: userId },
44+
displayName,
45+
new AzureCommunicationTokenCredential(token),
46+
threadId
47+
)
48+
);
49+
setCallAdapter(
50+
await createAzureCommunicationCallAdapter(
51+
{ kind: 'communicationUser', communicationUserId: userId },
52+
displayName,
53+
new AzureCommunicationTokenCredential(token),
54+
{ groupId }
55+
)
56+
);
57+
};
58+
createAdapter();
59+
}, []);
60+
61+
if (!!callAdapter && !!chatAdapter) {
62+
return (
63+
<>
64+
{chatAdapter && <ChatComposite adapter={chatAdapter} locale={fr_FR} />}
65+
{callAdapter && <CallComposite adapter={callAdapter} locale={fr_FR} />}
66+
</>
67+
);
68+
}
69+
if (credential === undefined) {
70+
return <h3>Failed to construct credential. Provided token is malformed.</h3>;
71+
}
72+
return <h3>Initializing...</h3>;
73+
}
74+
75+
export default App;

0 commit comments

Comments
 (0)