stringjet_ota_sdk 0.2.2 copy "stringjet_ota_sdk: ^0.2.2" to clipboard
stringjet_ota_sdk: ^0.2.2 copied to clipboard

Flutter SDK wrapper for StringJet OTA translations.

example/lib/main.dart

import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import 'stringjet_ffi.dart';

/// Same demo project as `samples/import-fixtures` (Android / iOS / React samples).
const String kStringJetSdkToken = 'a2dcfedf-dbff-4074-b805-59cac6d7b5e6';
const String kStringJetProjectId = 'e391c041-59e4-436e-a715-79ace250c7f3';

const String kBundledArbAsset = 'assets/import_fixtures/app_en.arb';

const List<String> kFixtureKeys = [
  'hard_welcome',
  'hard_named',
  'hard_percent',
  'hard_quote',
  'hard_plural_apples',
];

/// Demo values for known ARB placeholder names (matches Android/iOS sample args).
const Map<String, String> kArbDemoPlaceholders = {
  'ps1': 'Sam',
  'pi2': '3',
  'pf3': '19.99',
  'user': 'StringJet',
  'as0': '7',
  'ai0': '7',
  'name': 'StringJet',
  'count': '3',
};

/// OTA URL the Kotlin/Native FFI layer uses (`PlatformIds.FLUTTER`); log-only aid for debugging.
String expectedFlutterOtaUrl({bool prerelease = false}) {
  const base = 'https://cdn.stringjet.com';
  final enc = Uri.encodeQueryComponent(kStringJetSdkToken);
  final file = prerelease ? 'latest-prerelease.json' : 'latest.json';
  return '$base/$kStringJetProjectId/bundles/flutter/$file?token=$enc';
}

/// Whole `{identifier}` tokens only — never replace `{user}` inside `{username}`.
final RegExp _arbPlaceholder = RegExp(r'\{([a-zA-Z_][a-zA-Z0-9_]*)\}');

String applyArbDemoPlaceholders(String raw) {
  return raw.replaceAllMapped(_arbPlaceholder, (m) {
    final name = m.group(1)!;
    return kArbDemoPlaceholders[name] ?? m.group(0)!;
  });
}

/// Fills placeholders in Flutter ARB-style templates (OTA or bundled).
String formatArbDemoLine(String key, String? raw) {
  if (raw == null || raw.isEmpty) return '—';
  if (key == 'hard_plural_apples') {
    const count = 3;
    try {
      final j = jsonDecode(raw) as Map<String, dynamic>;
      final branch = count == 1 ? 'one' : 'other';
      final tpl = (j[branch] ?? j['other'] ?? raw).toString();
      return applyArbDemoPlaceholders(tpl.replaceAll('{count}', '$count'));
    } catch (_) {
      return applyArbDemoPlaceholders(raw);
    }
  }
  return applyArbDemoPlaceholders(raw);
}

Future<Map<String, String>> loadBundledArbEn() async {
  try {
    final text = await rootBundle.loadString(kBundledArbAsset);
    final root = jsonDecode(text) as Map<String, dynamic>;
    final out = <String, String>{};
    for (final e in root.entries) {
      final k = e.key;
      if (k.startsWith('@@') || k.startsWith('@')) continue;
      final v = e.value;
      if (v is String) out[k] = v;
    }
    return out;
  } catch (e, st) {
    debugPrint('[StringJetFlutter] bundled ARB load failed: $e\n$st');
    return {};
  }
}

void _log(String msg) => debugPrint('[StringJetFlutter] $msg');

void main() {
  runApp(const StringJetSampleApp());
}

class StringJetSampleApp extends StatelessWidget {
  const StringJetSampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'StringJet Flutter sample',
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
  List<String> _lines = [];
  bool _loading = true;
  StringJetNative? _native;
  Map<String, String> _bundledEn = {};

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _load();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      _log('App resumed — call _load() again if you add a refresh control; FFI cache is in native memory.');
    }
  }

  Future<void> _load() async {
    _log('--- load start ---');
    _bundledEn = await loadBundledArbEn();
    if (_bundledEn.isEmpty) {
      _log('Bundled ARB empty or missing — add $kBundledArbAsset to pubspec assets.');
    } else {
      _log('Bundled ARB keys: ${_bundledEn.keys.join(", ")}');
    }
    _log('defaultTargetPlatform=$defaultTargetPlatform kIsWeb=$kIsWeb');
    _log('Expected OTA URL (native side): ${expectedFlutterOtaUrl()}');
    _log('Also watch Xcode/Console for Kotlin [StringJet] / StringJetOtaSdk lines when using FFI.');

    final native = StringJetNative.open();
    _native = null;
    if (native == null) {
      _log('FFI: library not loaded — no native OTA; showing bundled ARB only if present.');
      setState(() {
        _loading = false;
        _lines = [
          'FFI only: native library not loaded.',
          '',
          'Filter console for: StringJetFFI (Dart) and [StringJet] (Kotlin in worker).',
          if (_bundledEn.isNotEmpty) ...[
            '',
            'Bundled import-fixtures (no FFI):',
            ...kFixtureKeys.map((k) => '$k → ${formatArbDemoLine(k, _bundledEn[k])}'),
          ] else ...[
            '',
            if (kIsWeb)
              'Web has no dart:ffi; use the JS SDK sample.'
            else if (defaultTargetPlatform == TargetPlatform.macOS)
              'macOS: from repo run ./gradlew :otaSdk:copyStringjetSdkDylibFlutterMacSample (copies libstringjet_sdk.dylib into macos/Runner; Xcode embeds it under Contents/Frameworks).'
            else if (defaultTargetPlatform == TargetPlatform.linux)
              'Linux: ./gradlew :otaSdk:linkDebugSharedLinuxX64 then install libstringjet_sdk.so next to the executable.'
            else if (defaultTargetPlatform == TargetPlatform.windows)
              'Windows: link stringjet_sdk.dll next to the executable.'
            else
              'This target is not wired for desktop FFI; use Android/iOS SDK samples.',
          ],
        ];
      });
      _log('--- load end (no library) ---');
      return;
    }

    final code = native.initEx(kStringJetSdkToken, kStringJetProjectId, fetchOnInit: 0, usePrerelease: 0);
    _native = native;
    if (code != 0) {
      _log('stringjet_init_ex -> $code (setup may have failed).');
    } else {
      _log('init_ex OK (no OTA fetch). Calling syncTranslations() …');
    }
    final syncCode = native.syncTranslations();
    if (syncCode != 0) {
      _log('syncTranslations -> $syncCode (OTA may have failed). Using native get() where present, else bundled ARB.');
    } else {
      _log('syncTranslations OK. App-bar refresh calls syncTranslations() again.');
    }
    await Future<void>.delayed(const Duration(milliseconds: 500));

    await _readKeysIntoLines(native);
    _log('--- load end ---');

  }

  Future<void> _readKeysIntoLines(StringJetNative native) async {
    const locale = 'en';
    final out = <String>[];
    for (final key in kFixtureKeys) {
      final fromNative = native.get(key, locale);
      final bundled = _bundledEn[key];
      final raw = (fromNative != null && fromNative.isNotEmpty) ? fromNative : bundled;
      if (fromNative == null || fromNative.isEmpty) {
        if (bundled != null) {
          _log('get("$key","$locale") miss or empty — bundled ARB fallback');
        } else {
          _log('get("$key","$locale") miss and no bundled string');
        }
      }
      out.add('$key → ${formatArbDemoLine(key, raw)}');
    }
    setState(() {
      _loading = false;
      _lines = out;
    });
  }

  Future<void> _onRefreshPressed() async {
    final n = _native;
    if (n == null) {
      _log('Refresh: no native handle; running full _load()');
      setState(() => _loading = true);
      await _load();
      return;
    }
    setState(() => _loading = true);
    _log('Refresh: syncTranslations() …');
    final r = n.syncTranslations();
    _log('syncTranslations -> $r');
    await Future<void>.delayed(const Duration(milliseconds: 200));
    await _readKeysIntoLines(n);
    _log('--- refresh end ---');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('import-fixtures (Flutter)'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            tooltip: 'OTA: syncTranslations() then re-read keys',
            onPressed: _onRefreshPressed,
          ),
        ],
      ),
      body: _loading
          ? const Center(child: CircularProgressIndicator())
          : ListView(
              padding: const EdgeInsets.all(24),
              children: [
                const Text(
                  'samples/import-fixtures',
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
                ),
                const SizedBox(height: 16),
                ..._lines.map((l) => Padding(
                      padding: const EdgeInsets.only(bottom: 10),
                      child: Text(l),
                    )),
              ],
            ),
    );
  }
}
0
likes
110
points
10
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter SDK wrapper for StringJet OTA translations.

Homepage
Repository (GitHub)
View/report issues

Topics

#localization #i18n #ota #flutter

License

unknown (license)

Dependencies

ffi, flutter, flutter_web_plugins

More

Packages that depend on stringjet_ota_sdk

Packages that implement stringjet_ota_sdk