Skip to content

Commit e35b362

Browse files
committed
Added custom localization support
1 parent 71d748a commit e35b362

10 files changed

Lines changed: 116 additions & 4 deletions

File tree

android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/NoCodesPlugin.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,15 @@ class NoCodesPlugin(private val messenger: BinaryMessenger, private val context:
6464
val projectKey = args["projectKey"] as? String ?: return result.noNecessaryDataError()
6565
val version = args["version"] as? String ?: return result.noNecessaryDataError()
6666
val source = args["source"] as? String ?: return result.noNecessaryDataError()
67+
val locale = args["locale"] as? String
6768

6869
if (projectKey.isNotEmpty()) {
6970
// Initialize NoCodes Sandwich
7071
noCodesSandwich = NoCodesSandwich()
7172

7273
noCodesSandwich?.storeSdkInfo(context, source, version)
7374

74-
noCodesSandwich?.initialize(context, projectKey)
75+
noCodesSandwich?.initialize(context, projectKey, null, null, null, locale)
7576
noCodesSandwich?.setDelegate(this)
7677
result.success(null)
7778
} else {
@@ -102,6 +103,11 @@ class NoCodesPlugin(private val messenger: BinaryMessenger, private val context:
102103
result.success(null)
103104
}
104105

106+
fun setLocale(locale: String?, result: Result) {
107+
noCodesSandwich?.setLocale(locale)
108+
result.success(null)
109+
}
110+
105111
// NoCodesEventListener implementation
106112
override fun onNoCodesEvent(event: NoCodesEventListener.Event, payload: BridgeData?) {
107113
val eventData = mapOf("payload" to (payload ?: emptyMap<String, Any>()))

android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/QonversionPlugin.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class QonversionPlugin : MethodCallHandler, FlutterPlugin, ActivityAware {
137137
"initializeNoCodes" -> noCodesPlugin?.initializeNoCodes(args, result)
138138
"setScreenPresentationConfig" -> noCodesPlugin?.setScreenPresentationConfig(args["config"] as? Map<String, Any>, args["contextKey"] as? String, result)
139139
"showNoCodesScreen" -> noCodesPlugin?.showNoCodesScreen(args["contextKey"] as? String, result)
140+
"setNoCodesLocale" -> noCodesPlugin?.setLocale(args["locale"] as? String, result)
140141
else -> result.notImplemented()
141142
}
142143
}

example/lib/screens/no_codes_screen.dart

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ class NoCodesScreen extends StatefulWidget {
1616

1717
class _NoCodesScreenState extends State<NoCodesScreen> {
1818
final _contextKeyController = TextEditingController(text: 'kamo_test');
19+
final _localeController = TextEditingController();
1920

2021
NoCodesPresentationStyle _presentationStyle = NoCodesPresentationStyle.fullScreen;
2122
bool _animated = true;
2223

2324
@override
2425
void dispose() {
2526
_contextKeyController.dispose();
27+
_localeController.dispose();
2628
super.dispose();
2729
}
2830

@@ -53,6 +55,8 @@ class _NoCodesScreenState extends State<NoCodesScreen> {
5355
const SizedBox(height: 16),
5456
_buildPresentationConfigSection(),
5557
const SizedBox(height: 16),
58+
_buildLocaleSection(),
59+
const SizedBox(height: 16),
5660
_buildActionsSection(),
5761
const SizedBox(height: 16),
5862
_buildEventsSection(appState),
@@ -149,6 +153,36 @@ class _NoCodesScreenState extends State<NoCodesScreen> {
149153
);
150154
}
151155

156+
Widget _buildLocaleSection() {
157+
return SectionCard(
158+
title: 'Locale',
159+
child: Column(
160+
crossAxisAlignment: CrossAxisAlignment.stretch,
161+
children: [
162+
TextField(
163+
controller: _localeController,
164+
decoration: const InputDecoration(
165+
hintText: 'e.g. en, de, fr',
166+
labelText: 'Locale code',
167+
),
168+
),
169+
const SizedBox(height: 12),
170+
ElevatedButton.icon(
171+
onPressed: _setLocale,
172+
icon: const Icon(Icons.language),
173+
label: const Text('Set Locale'),
174+
),
175+
const SizedBox(height: 8),
176+
OutlinedButton.icon(
177+
onPressed: _resetLocale,
178+
icon: const Icon(Icons.refresh),
179+
label: const Text('Reset to Device Default'),
180+
),
181+
],
182+
),
183+
);
184+
}
185+
152186
Widget _buildActionsSection() {
153187
return SectionCard(
154188
title: 'Actions',
@@ -246,6 +280,34 @@ class _NoCodesScreenState extends State<NoCodesScreen> {
246280
}
247281
}
248282

283+
void _setLocale() async {
284+
final locale = _localeController.text.trim();
285+
final localeValue = locale.isEmpty ? null : locale;
286+
287+
try {
288+
debugPrint('🔄 [NoCodes] Setting locale: $localeValue');
289+
await NoCodes.getSharedInstance().setLocale(localeValue);
290+
debugPrint('✅ [NoCodes] Locale set');
291+
_showSuccess(localeValue != null ? 'Locale set to: $localeValue' : 'Locale reset to device default');
292+
} catch (e) {
293+
debugPrint('❌ [NoCodes] Failed to set locale: $e');
294+
_showError('Failed to set locale: $e');
295+
}
296+
}
297+
298+
void _resetLocale() async {
299+
try {
300+
debugPrint('🔄 [NoCodes] Resetting locale to device default...');
301+
await NoCodes.getSharedInstance().setLocale(null);
302+
_localeController.clear();
303+
debugPrint('✅ [NoCodes] Locale reset');
304+
_showSuccess('Locale reset to device default');
305+
} catch (e) {
306+
debugPrint('❌ [NoCodes] Failed to reset locale: $e');
307+
_showError('Failed to reset locale: $e');
308+
}
309+
}
310+
249311
void _showSuccess(String message) {
250312
ScaffoldMessenger.of(context).showSnackBar(
251313
SnackBar(content: Text(message), backgroundColor: AppTheme.successColor),

ios/Classes/NoCodesPlugin.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ public class NoCodesPlugin: NSObject {
7272
}
7373

7474
let proxyUrl = args["proxyUrl"] as? String
75-
noCodesSandwich?.initialize(projectKey: projectKey, proxyUrl: proxyUrl)
75+
let locale = args["locale"] as? String
76+
noCodesSandwich?.initialize(projectKey: projectKey, proxyUrl: proxyUrl, locale: locale)
7677
result(nil)
7778
}
7879

@@ -102,6 +103,11 @@ public class NoCodesPlugin: NSObject {
102103
noCodesSandwich?.close()
103104
result(nil)
104105
}
106+
107+
public func setLocale(_ locale: String?, _ result: @escaping FlutterResult) {
108+
noCodesSandwich?.setLocale(locale)
109+
result(nil)
110+
}
105111
}
106112

107113
extension NoCodesPlugin: NoCodesEventListener {

ios/Classes/SwiftQonversionPlugin.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ public class SwiftQonversionPlugin: NSObject, FlutterPlugin {
168168
case "showNoCodesScreen":
169169
noCodesPlugin?.showScreen(args, result)
170170
return
171+
172+
case "setNoCodesLocale":
173+
noCodesPlugin?.setLocale(args["locale"] as? String, result)
174+
return
171175

172176
default:
173177
return result(FlutterMethodNotImplemented)

lib/src/internal/constants.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class Constants {
3535
static const kConfig = 'config';
3636
static const kVersion = 'version';
3737
static const kSource = 'source';
38+
static const kLocale = 'locale';
3839

3940
// MethodChannel methods names
4041
static const mInitialize = 'initialize';
@@ -76,6 +77,7 @@ class Constants {
7677
static const mSetScreenPresentationConfig = 'setScreenPresentationConfig';
7778
static const mShowNoCodesScreen = 'showNoCodesScreen';
7879
static const mCloseNoCodes = 'closeNoCodes';
80+
static const mSetNoCodesLocale = 'setNoCodesLocale';
7981

8082
// Other constants
8183
static const skuDetailsPriceRatio = 1000000;

lib/src/internal/nocodes_internal.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class NoCodesInternal implements NoCodes {
3737
Constants.kVersion: QonversionInternal.sdkVersion,
3838
Constants.kSource: Constants.sdkSource,
3939
if (config.proxyUrl != null) Constants.kProxyUrl: config.proxyUrl,
40+
if (config.locale != null) Constants.kLocale: config.locale,
4041
};
4142
// Initialize is fire-and-forget, errors will be handled in subsequent calls
4243
_channel.invokeMethod(Constants.mInitializeNoCodes, args).catchError((error) {
@@ -166,6 +167,15 @@ class NoCodesInternal implements NoCodes {
166167
await _invokeMethod(Constants.mCloseNoCodes);
167168
}
168169

170+
@override
171+
Future<void> setLocale(String? locale) async {
172+
if (Platform.isMacOS) {
173+
return;
174+
}
175+
176+
await _invokeMethod(Constants.mSetNoCodesLocale, {Constants.kLocale: locale});
177+
}
178+
169179
/// Invokes a method on the platform channel and converts PlatformException to QonversionException
170180
Future<dynamic> _invokeMethod(String method, [Map<String, dynamic>? arguments]) async {
171181
try {

lib/src/nocodes.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,13 @@ abstract class NoCodes {
100100
/// **Platform Support:** iOS and Android. No-op on macOS.
101101
Future<void> close();
102102

103+
/// Set the locale for No-Code screens.
104+
/// Use this to override the device locale for the No-Codes SDK.
105+
/// Pass null to reset to the device default locale.
106+
///
107+
/// **Platform Support:** iOS and Android. No-op on macOS.
108+
///
109+
/// [locale] the locale to use (e.g. "en", "de", "fr"), or null to reset to device default.
110+
Future<void> setLocale(String? locale);
111+
103112
}

lib/src/nocodes_config.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
class NoCodesConfig {
33
final String projectKey;
44
final String? proxyUrl;
5+
final String? locale;
56

6-
const NoCodesConfig(this.projectKey, {this.proxyUrl});
7+
const NoCodesConfig(this.projectKey, {this.proxyUrl, this.locale});
78
}

lib/src/nocodes_config_builder.dart

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'nocodes_config.dart';
44
class NoCodesConfigBuilder {
55
final String projectKey;
66
String? proxyUrl;
7+
String? locale;
78

89
NoCodesConfigBuilder(this.projectKey);
910

@@ -16,10 +17,20 @@ class NoCodesConfigBuilder {
1617
return this;
1718
}
1819

20+
/// Set the locale for No-Code screens.
21+
/// Use this to override the device locale for the No-Codes SDK.
22+
///
23+
/// [locale] the locale to use (e.g. "en", "de", "fr").
24+
/// Returns the builder instance for method chaining.
25+
NoCodesConfigBuilder setLocale(String locale) {
26+
this.locale = locale;
27+
return this;
28+
}
29+
1930
/// Generate [NoCodesConfig] instance with all the provided configurations.
2031
///
2132
/// Returns the complete [NoCodesConfig] instance.
2233
NoCodesConfig build() {
23-
return NoCodesConfig(projectKey, proxyUrl: proxyUrl);
34+
return NoCodesConfig(projectKey, proxyUrl: proxyUrl, locale: locale);
2435
}
2536
}

0 commit comments

Comments
 (0)