@amplytools/react-native-amply-sdk 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AmplyReactNative.podspec +1 -1
- package/README.md +6 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/tools/amply/sdk/reactnative/AmplyModule.kt +8 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/core/AmplyClient.kt +2 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/core/DefaultAmplyClient.kt +17 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/model/AmplyInitializationOptions.kt +2 -0
- package/android/src/newarch/java/tools/amply/sdk/reactnative/NativeAmplyModuleSpec.java +4 -0
- package/android/src/newarch/jni/AmplyReactNative-generated.cpp +6 -0
- package/dist/src/__tests__/appLanguage.test.d.ts +43 -0
- package/dist/src/__tests__/appLanguage.test.d.ts.map +1 -0
- package/dist/src/__tests__/appLanguage.test.js +211 -0
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +78 -1
- package/dist/src/nativeSpecs/NativeAmplyModule.d.ts +12 -0
- package/dist/src/nativeSpecs/NativeAmplyModule.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/ARCHITECTURE.md +2 -0
- package/ios/Sources/AmplyReactNative/AmplyModule.mm +25 -0
- package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative-generated.mm +7 -0
- package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative.h +7 -0
- package/package.json +1 -1
- package/src/__tests__/appLanguage.test.ts +296 -0
- package/src/index.ts +99 -1
- package/src/nativeSpecs/NativeAmplyModule.ts +12 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `setAppLanguage` and the `appLanguage` initialization option.
|
|
3
|
+
*
|
|
4
|
+
* The SDK treats both as one setting with "last write wins": `appLanguage` in the config is the
|
|
5
|
+
* same as calling `setAppLanguage` at initialization. The native bridges cannot honour that order
|
|
6
|
+
* on their own for a call made BEFORE `initialize` — they buffer it and replay it after the SDK is
|
|
7
|
+
* built, which would let an earlier call overwrite the later config value. So the JS layer parks a
|
|
8
|
+
* pre-init call itself and folds it into the config it hands to native.
|
|
9
|
+
*
|
|
10
|
+
* Each test loads a fresh copy of the module: the parked value is module state, and leaking it
|
|
11
|
+
* from one test into the next would make every ordering assertion here meaningless.
|
|
12
|
+
*/
|
|
13
|
+
type AmplyModule = typeof import('../index');
|
|
14
|
+
|
|
15
|
+
function createNativeModule() {
|
|
16
|
+
let initialized = false;
|
|
17
|
+
return {
|
|
18
|
+
initialize: jest.fn(async () => {
|
|
19
|
+
initialized = true;
|
|
20
|
+
}),
|
|
21
|
+
isInitialized: jest.fn(() => initialized),
|
|
22
|
+
setAppLanguage: jest.fn(),
|
|
23
|
+
setUserId: jest.fn(),
|
|
24
|
+
addListener: jest.fn(),
|
|
25
|
+
removeListeners: jest.fn(),
|
|
26
|
+
onSystemEvent: jest.fn(() => ({remove: jest.fn()})),
|
|
27
|
+
onDeepLink: jest.fn(() => ({remove: jest.fn()})),
|
|
28
|
+
onCampaignPresent: jest.fn(() => ({remove: jest.fn()})),
|
|
29
|
+
/** Test-only: pretend the SDK is (or is not) up without going through initialize. */
|
|
30
|
+
setInitialized(value: boolean) {
|
|
31
|
+
initialized = value;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type NativeModuleMock = ReturnType<typeof createNativeModule>;
|
|
37
|
+
|
|
38
|
+
const KEYS = {appId: 'id', apiKeyPublic: 'public', apiKeySecret: 'secret'};
|
|
39
|
+
|
|
40
|
+
function load(): {Amply: AmplyModule; native: NativeModuleMock} {
|
|
41
|
+
let Amply!: AmplyModule;
|
|
42
|
+
const native = createNativeModule();
|
|
43
|
+
jest.isolateModules(() => {
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
45
|
+
require('../nativeModule').__setNativeModule(native as never);
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
47
|
+
Amply = require('../index');
|
|
48
|
+
});
|
|
49
|
+
return {Amply, native};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('setAppLanguage after initialization', () => {
|
|
53
|
+
it.each(['pt_BR', 'zh-TW', 'FR', 'Français'])(
|
|
54
|
+
'forwards %p to the native SDK verbatim — parsing and the warning for junk are the SDK’s job',
|
|
55
|
+
tag => {
|
|
56
|
+
const {Amply, native} = load();
|
|
57
|
+
native.setInitialized(true);
|
|
58
|
+
|
|
59
|
+
Amply.setAppLanguage(tag);
|
|
60
|
+
|
|
61
|
+
expect(native.setAppLanguage).toHaveBeenCalledTimes(1);
|
|
62
|
+
expect(native.setAppLanguage).toHaveBeenCalledWith(tag);
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
it('forwards null, which returns the SDK to automatic detection', () => {
|
|
67
|
+
const {Amply, native} = load();
|
|
68
|
+
native.setInitialized(true);
|
|
69
|
+
|
|
70
|
+
Amply.setAppLanguage(null);
|
|
71
|
+
|
|
72
|
+
expect(native.setAppLanguage).toHaveBeenCalledWith(null);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('is on the default export as well', () => {
|
|
76
|
+
const {Amply, native} = load();
|
|
77
|
+
native.setInitialized(true);
|
|
78
|
+
|
|
79
|
+
Amply.default.setAppLanguage('de');
|
|
80
|
+
|
|
81
|
+
expect(native.setAppLanguage).toHaveBeenCalledWith('de');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('appLanguage in the initialization config', () => {
|
|
86
|
+
it('is passed through to the native initialize', async () => {
|
|
87
|
+
const {Amply, native} = load();
|
|
88
|
+
|
|
89
|
+
await Amply.initialize({...KEYS, appLanguage: 'pt_BR'});
|
|
90
|
+
|
|
91
|
+
expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({appLanguage: 'pt_BR'}));
|
|
92
|
+
expect(native.setAppLanguage).not.toHaveBeenCalled();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('is left out of the native config when the caller did not give one', async () => {
|
|
96
|
+
const {Amply, native} = load();
|
|
97
|
+
|
|
98
|
+
await Amply.initialize(KEYS);
|
|
99
|
+
|
|
100
|
+
const [config] = native.initialize.mock.calls[0] as unknown as [Record<string, unknown>];
|
|
101
|
+
expect(config).not.toHaveProperty('appLanguage');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('setAppLanguage before initialization', () => {
|
|
106
|
+
it('is not sent to a native SDK that is not up yet', () => {
|
|
107
|
+
const {Amply, native} = load();
|
|
108
|
+
|
|
109
|
+
Amply.setAppLanguage('fr');
|
|
110
|
+
|
|
111
|
+
expect(native.setAppLanguage).not.toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('reaches the SDK as the config appLanguage when initialize names none', async () => {
|
|
115
|
+
const {Amply, native} = load();
|
|
116
|
+
Amply.setAppLanguage('fr');
|
|
117
|
+
|
|
118
|
+
await Amply.initialize(KEYS);
|
|
119
|
+
|
|
120
|
+
expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({appLanguage: 'fr'}));
|
|
121
|
+
expect(native.setAppLanguage).not.toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('keeps only the last of several calls', async () => {
|
|
125
|
+
const {Amply, native} = load();
|
|
126
|
+
Amply.setAppLanguage('fr');
|
|
127
|
+
Amply.setAppLanguage('de');
|
|
128
|
+
|
|
129
|
+
await Amply.initialize(KEYS);
|
|
130
|
+
|
|
131
|
+
expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({appLanguage: 'de'}));
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('loses to an appLanguage given to the later initialize call', async () => {
|
|
135
|
+
const {Amply, native} = load();
|
|
136
|
+
Amply.setAppLanguage('fr');
|
|
137
|
+
|
|
138
|
+
await Amply.initialize({...KEYS, appLanguage: 'de'});
|
|
139
|
+
|
|
140
|
+
expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({appLanguage: 'de'}));
|
|
141
|
+
expect(native.setAppLanguage).not.toHaveBeenCalled();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('wins over a blank appLanguage in the config, which names no language', async () => {
|
|
145
|
+
const {Amply, native} = load();
|
|
146
|
+
Amply.setAppLanguage('fr');
|
|
147
|
+
|
|
148
|
+
await Amply.initialize({...KEYS, appLanguage: ' '});
|
|
149
|
+
|
|
150
|
+
expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({appLanguage: 'fr'}));
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// null cannot travel in the config — there it means "leave the setting alone" — so a parked
|
|
154
|
+
// clear is applied as a call once the SDK is up.
|
|
155
|
+
it('applies a parked null right after initialization', async () => {
|
|
156
|
+
const {Amply, native} = load();
|
|
157
|
+
Amply.setAppLanguage(null);
|
|
158
|
+
|
|
159
|
+
await Amply.initialize(KEYS);
|
|
160
|
+
|
|
161
|
+
const [config] = native.initialize.mock.calls[0] as unknown as [Record<string, unknown>];
|
|
162
|
+
expect(config).not.toHaveProperty('appLanguage');
|
|
163
|
+
expect(native.setAppLanguage).toHaveBeenCalledTimes(1);
|
|
164
|
+
expect(native.setAppLanguage).toHaveBeenCalledWith(null);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('drops a parked null when the later initialize names a language', async () => {
|
|
168
|
+
const {Amply, native} = load();
|
|
169
|
+
Amply.setAppLanguage(null);
|
|
170
|
+
|
|
171
|
+
await Amply.initialize({...KEYS, appLanguage: 'de'});
|
|
172
|
+
|
|
173
|
+
expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({appLanguage: 'de'}));
|
|
174
|
+
expect(native.setAppLanguage).not.toHaveBeenCalled();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('survives an initialize that fails, and is used by the retry', async () => {
|
|
178
|
+
const {Amply, native} = load();
|
|
179
|
+
Amply.setAppLanguage('fr');
|
|
180
|
+
native.initialize.mockImplementationOnce(async () => {
|
|
181
|
+
throw new Error('AMP_INIT_FAILED');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
await expect(Amply.initialize(KEYS)).rejects.toThrow('AMP_INIT_FAILED');
|
|
185
|
+
await Amply.initialize(KEYS);
|
|
186
|
+
|
|
187
|
+
expect(native.initialize).toHaveBeenLastCalledWith(expect.objectContaining({appLanguage: 'fr'}));
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('survives an initialize rejected for a missing key', async () => {
|
|
191
|
+
const {Amply, native} = load();
|
|
192
|
+
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
193
|
+
Amply.setAppLanguage('fr');
|
|
194
|
+
|
|
195
|
+
await expect(Amply.initialize({...KEYS, apiKeySecret: ''})).rejects.toMatchObject({
|
|
196
|
+
code: 'AMP_INVALID_CONFIG',
|
|
197
|
+
});
|
|
198
|
+
await Amply.initialize(KEYS);
|
|
199
|
+
|
|
200
|
+
expect(native.initialize).toHaveBeenCalledTimes(1);
|
|
201
|
+
expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({appLanguage: 'fr'}));
|
|
202
|
+
error.mockRestore();
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('setAppLanguage while initialize is in flight', () => {
|
|
207
|
+
it('is applied after initialization, so it wins over the config value it came after', async () => {
|
|
208
|
+
const {Amply, native} = load();
|
|
209
|
+
let finishInit!: () => void;
|
|
210
|
+
native.initialize.mockImplementationOnce(
|
|
211
|
+
() =>
|
|
212
|
+
new Promise<void>(resolve => {
|
|
213
|
+
finishInit = () => {
|
|
214
|
+
native.setInitialized(true);
|
|
215
|
+
resolve();
|
|
216
|
+
};
|
|
217
|
+
}),
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const init = Amply.initialize({...KEYS, appLanguage: 'de'});
|
|
221
|
+
Amply.setAppLanguage('fr');
|
|
222
|
+
expect(native.setAppLanguage).not.toHaveBeenCalled();
|
|
223
|
+
finishInit();
|
|
224
|
+
await init;
|
|
225
|
+
|
|
226
|
+
expect(native.initialize).toHaveBeenCalledWith(expect.objectContaining({appLanguage: 'de'}));
|
|
227
|
+
expect(native.setAppLanguage).toHaveBeenCalledTimes(1);
|
|
228
|
+
expect(native.setAppLanguage).toHaveBeenCalledWith('fr');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// The native SDK reports itself up before the initialize promise settles, so a call made in
|
|
232
|
+
// that window goes straight through. A value held from before initialize is older than it and
|
|
233
|
+
// must not be replayed over it when the promise finally resolves.
|
|
234
|
+
function hangInitialize(native: NativeModuleMock): {
|
|
235
|
+
comeUp: () => void;
|
|
236
|
+
resolve: () => void;
|
|
237
|
+
reject: (error: Error) => void;
|
|
238
|
+
} {
|
|
239
|
+
const control = {comeUp: () => {}, resolve: () => {}, reject: (_error: Error) => {}};
|
|
240
|
+
native.initialize.mockImplementationOnce(
|
|
241
|
+
() =>
|
|
242
|
+
new Promise<void>((resolve, reject) => {
|
|
243
|
+
control.comeUp = () => native.setInitialized(true);
|
|
244
|
+
control.resolve = resolve;
|
|
245
|
+
control.reject = reject;
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
return control;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
it('does not replay an older held null over a call the SDK already received', async () => {
|
|
252
|
+
const {Amply, native} = load();
|
|
253
|
+
Amply.setAppLanguage(null);
|
|
254
|
+
const init = hangInitialize(native);
|
|
255
|
+
|
|
256
|
+
const pending = Amply.initialize(KEYS);
|
|
257
|
+
init.comeUp();
|
|
258
|
+
Amply.setAppLanguage('fr');
|
|
259
|
+
init.resolve();
|
|
260
|
+
await pending;
|
|
261
|
+
|
|
262
|
+
expect(native.setAppLanguage.mock.calls).toEqual([['fr']]);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('does not replay an older held tag over a call the SDK already received', async () => {
|
|
266
|
+
const {Amply, native} = load();
|
|
267
|
+
const init = hangInitialize(native);
|
|
268
|
+
|
|
269
|
+
const pending = Amply.initialize(KEYS);
|
|
270
|
+
Amply.setAppLanguage('de');
|
|
271
|
+
init.comeUp();
|
|
272
|
+
Amply.setAppLanguage('fr');
|
|
273
|
+
init.resolve();
|
|
274
|
+
await pending;
|
|
275
|
+
|
|
276
|
+
expect(native.setAppLanguage.mock.calls).toEqual([['fr']]);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('does not hand a superseded held tag to the retry of a failed initialize', async () => {
|
|
280
|
+
const {Amply, native} = load();
|
|
281
|
+
Amply.setAppLanguage('de');
|
|
282
|
+
const init = hangInitialize(native);
|
|
283
|
+
|
|
284
|
+
const pending = Amply.initialize(KEYS);
|
|
285
|
+
init.comeUp();
|
|
286
|
+
Amply.setAppLanguage('fr');
|
|
287
|
+
init.reject(new Error('AMP_INIT_FAILED'));
|
|
288
|
+
await expect(pending).rejects.toThrow('AMP_INIT_FAILED');
|
|
289
|
+
native.setInitialized(false);
|
|
290
|
+
await Amply.initialize(KEYS);
|
|
291
|
+
|
|
292
|
+
const [retryConfig] = native.initialize.mock.calls[1] as unknown as [Record<string, unknown>];
|
|
293
|
+
expect(retryConfig).not.toHaveProperty('appLanguage');
|
|
294
|
+
expect(native.setAppLanguage.mock.calls).toEqual([['fr']]);
|
|
295
|
+
});
|
|
296
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -232,11 +232,108 @@ export async function initialize(config: AmplyInitializationConfig): Promise<voi
|
|
|
232
232
|
if (config.debug || config.logLevel) {
|
|
233
233
|
ensureDebugLogListener();
|
|
234
234
|
}
|
|
235
|
-
|
|
235
|
+
// Taken now, synchronously: a call made after this point is later than the config and must
|
|
236
|
+
// stay parked so it is applied after it.
|
|
237
|
+
const parked = takeParkedAppLanguage();
|
|
238
|
+
try {
|
|
239
|
+
await getNativeModule().initialize(withParkedAppLanguage(config, parked));
|
|
240
|
+
} catch (error) {
|
|
241
|
+
// Nothing reached the SDK, so the call still has to — unless a newer one replaced it.
|
|
242
|
+
if (parkedAppLanguage == null && isLatestLanguageWrite(parked)) {
|
|
243
|
+
parkedAppLanguage = parked;
|
|
244
|
+
}
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
// A parked clear cannot travel in the config, where null means "leave it alone". Replayed only
|
|
248
|
+
// if nothing newer came in while initialize was in flight: the native SDK reports itself up
|
|
249
|
+
// before this promise settles, and a call made in that window has already reached it.
|
|
250
|
+
if (parked?.tag === null && !hasText(config.appLanguage) && isLatestLanguageWrite(parked)) {
|
|
251
|
+
getNativeModule().setAppLanguage(null);
|
|
252
|
+
}
|
|
253
|
+
flushParkedAppLanguage();
|
|
236
254
|
// Anything the host registered before this point was parked rather than lost.
|
|
237
255
|
replayPendingRegistrations();
|
|
238
256
|
}
|
|
239
257
|
|
|
258
|
+
/**
|
|
259
|
+
* A {@link setAppLanguage} call made before the SDK was up. Boxed so that a parked `null` — a
|
|
260
|
+
* request to return to automatic detection — is distinguishable from nothing parked.
|
|
261
|
+
*
|
|
262
|
+
* The native bridges buffer such calls too, but replay them AFTER building the SDK, which applies
|
|
263
|
+
* the config's `appLanguage` first. An earlier call would then overwrite a later config value,
|
|
264
|
+
* the opposite of "last write wins". Parking it here lets {@link initialize} put the two in the
|
|
265
|
+
* order they were made.
|
|
266
|
+
*/
|
|
267
|
+
let parkedAppLanguage: LanguageWrite | null = null;
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* One {@link setAppLanguage} call. `seq` orders it against every other call, forwarded or held:
|
|
271
|
+
* a held value is applied only while it is still the latest write, so one taken before
|
|
272
|
+
* initialize cannot be replayed over a call the native SDK received while initialize was in
|
|
273
|
+
* flight.
|
|
274
|
+
*/
|
|
275
|
+
interface LanguageWrite {
|
|
276
|
+
readonly tag: string | null;
|
|
277
|
+
readonly seq: number;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Bumped by every {@link setAppLanguage} call, whether it is forwarded or held. */
|
|
281
|
+
let languageWriteSeq = 0;
|
|
282
|
+
|
|
283
|
+
function isLatestLanguageWrite(write: LanguageWrite | null): write is LanguageWrite {
|
|
284
|
+
return write != null && write.seq === languageWriteSeq;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function hasText(value: string | null | undefined): value is string {
|
|
288
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function takeParkedAppLanguage(): LanguageWrite | null {
|
|
292
|
+
const parked = parkedAppLanguage;
|
|
293
|
+
parkedAppLanguage = null;
|
|
294
|
+
return parked;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** The config's own appLanguage wins: initialize was called after the parked call. */
|
|
298
|
+
function withParkedAppLanguage(
|
|
299
|
+
config: AmplyInitializationConfig,
|
|
300
|
+
parked: LanguageWrite | null,
|
|
301
|
+
): AmplyInitializationConfig {
|
|
302
|
+
if (hasText(config.appLanguage) || parked == null || parked.tag === null) {
|
|
303
|
+
return config;
|
|
304
|
+
}
|
|
305
|
+
return {...config, appLanguage: parked.tag};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function flushParkedAppLanguage(): void {
|
|
309
|
+
const parked = takeParkedAppLanguage();
|
|
310
|
+
if (isLatestLanguageWrite(parked)) {
|
|
311
|
+
getNativeModule().setAppLanguage(parked.tag);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Tell Amply the language your app's UI is in, so sheets, stories and mini-apps show in the
|
|
317
|
+
* matching language and campaigns can target it. Use this when the app chooses its language
|
|
318
|
+
* itself (an in-app language picker, i18next, …) rather than following the device.
|
|
319
|
+
*
|
|
320
|
+
* Any common tag format works — `'pt_BR'`, `'pt-BR'`, `'zh-TW'`, `'fr'`, any case. A value that
|
|
321
|
+
* is not a language tag is ignored by the SDK with a warning log. The value is persisted across
|
|
322
|
+
* launches; pass `null` to return to automatic detection. The device language is never changed.
|
|
323
|
+
*
|
|
324
|
+
* Safe to call before {@link initialize}: the value is held and applied at initialization, and
|
|
325
|
+
* an `appLanguage` passed to a later `initialize` call wins over it.
|
|
326
|
+
* @param tag The language tag, or null to clear
|
|
327
|
+
*/
|
|
328
|
+
export function setAppLanguage(tag: string | null): void {
|
|
329
|
+
languageWriteSeq += 1;
|
|
330
|
+
if (isSdkUp()) {
|
|
331
|
+
getNativeModule().setAppLanguage(tag);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
parkedAppLanguage = {tag, seq: languageWriteSeq};
|
|
335
|
+
}
|
|
336
|
+
|
|
240
337
|
/**
|
|
241
338
|
* Set the user ID for analytics. Pass null to clear.
|
|
242
339
|
* @param userId The user ID to set, or null to clear
|
|
@@ -642,6 +739,7 @@ export default {
|
|
|
642
739
|
addSystemEventsListener,
|
|
643
740
|
removeAllListeners,
|
|
644
741
|
setUserId,
|
|
742
|
+
setAppLanguage,
|
|
645
743
|
setLogLevel,
|
|
646
744
|
getLogLevel,
|
|
647
745
|
setCustomProperty,
|
|
@@ -46,6 +46,12 @@ export type AmplyInitializationConfig = {
|
|
|
46
46
|
* Takes precedence over `debug` if both are specified.
|
|
47
47
|
*/
|
|
48
48
|
logLevel?: LogLevel | null;
|
|
49
|
+
/**
|
|
50
|
+
* The language the app's UI is in, in any common format (`'pt_BR'`, `'zh-TW'`, `'fr'`).
|
|
51
|
+
* The same as calling `setAppLanguage` right after initialization, and persisted the same
|
|
52
|
+
* way. Omitted, null or blank leaves the current setting alone.
|
|
53
|
+
*/
|
|
54
|
+
appLanguage?: string | null;
|
|
49
55
|
};
|
|
50
56
|
|
|
51
57
|
export type EventType = 'custom' | 'system';
|
|
@@ -202,6 +208,12 @@ export interface Spec extends TurboModule {
|
|
|
202
208
|
* @param userId The user ID to set, or null to clear
|
|
203
209
|
*/
|
|
204
210
|
setUserId(userId: string | null): void;
|
|
211
|
+
/**
|
|
212
|
+
* Set the language the app's UI is in. Any common tag format; null returns to automatic
|
|
213
|
+
* detection. Persisted by the SDK across launches.
|
|
214
|
+
* @param tag The language tag, or null to clear
|
|
215
|
+
*/
|
|
216
|
+
setAppLanguage(tag: string | null): void;
|
|
205
217
|
/**
|
|
206
218
|
* Set the log level at runtime.
|
|
207
219
|
* @param level The log level to set
|