easy localization translation is not working consistently sometimes its not translating the whole app why this issue happens?

302 Views Asked by At

this is asset loader class for fetching the translation.json file from api.

class SmartNetworkAssetLoader extends AssetLoader {
  final Function localeUrl;

  final Duration timeout;

  final String assetsPath;

  final Duration localCacheDuration;

  SmartNetworkAssetLoader(
      {required this.localeUrl,
        this.timeout = const Duration(seconds: 30),
        required this.assetsPath,
        this.localCacheDuration = const Duration(days: 1)});

  @override
  Future<Map<String, dynamic>> load(String localePath, Locale locale) async {
    var string = '';
     print("localPath $localePath");
     print("locale $locale");
    // try loading local previously-saved localization file
    if (await localTranslationExists(locale.toString())) {
      string = await loadFromLocalFile(locale.toString());
    }

    // no local or failed, check if internet and download the file
    if (string == '' && await isInternetConnectionAvailable()) {
      string = await loadFromNetwork(locale.toString());
    }

    // local cache duration was reached or no internet access but prefer local file to assets
    if (string == '' &&
        await localTranslationExists(locale.toString(),
            ignoreCacheDuration: false)) {
      string = await loadFromLocalFile(locale.toString());
    }

    // still nothing? Load from assets
    if (string == '') {
      String localeString = locale.toString();

      // Replace underscores with hyphens
      String convertedLocale = localeString.replaceAll('_', '-');
      string = await rootBundle
          .loadString('$assetsPath/$convertedLocale.json');
    }

    // then returns the json file
    return json.decode(string);
  }

  Future<bool> localeExists(String localePath) => Future.value(true);

  Future<bool> isInternetConnectionAvailable() async {
    try{
      ConnectivityResult connectivityResult =
      await (Connectivity().checkConnectivity());
      if (connectivityResult == ConnectivityResult.none) return false;

      final result = await Future.any(
          [InternetAddress.lookup('google.com'), Future.delayed(timeout)]);

      if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
        return true;
      }

    }catch(e)
    {
     print(e);
    }
    return false;
  }

  Future<String> loadFromNetwork(String localeName) async {
    String url = localeUrl(localeName);
     print("loccaleName$localeName");
     print("url $url");
    url = '$url$localeName';
     print("final Url $url");
    try {
      final response =
      await Future.any([http.get(Uri.parse(url)), Future.delayed(timeout)]);

      if (response != null && response.statusCode == 200) {
        var content = utf8.decode(response.bodyBytes);
        log("content1234 $content");
        try {
          var jsonResponse = json.decode(content);
          if (jsonResponse != null && jsonResponse['data'] != null) {
            // Extract only the "data" content
            var dataContent = jsonResponse['data'];
            // Convert it back to a JSON string
            var dataContentString = json.encode(dataContent);
            print("data content: $dataContentString");
            print("localeName $localeName");
            // Save the translation
            await saveTranslation(localeName, dataContentString);
            return dataContentString;
          }
        } catch (e) {
          showFalseSnackbar("$e");
          print("Error decoding JSON: $e");
        }
      }else{
        showFalseSnackbar("response null or ${response.statusCode}");
      }
    } catch (e) {
      showFalseSnackbar("$e");
      print(e.toString());
    }

    return '';
  }

  Future<bool> localTranslationExists(String localeName,
      {bool ignoreCacheDuration = false}) async {
    var translationFile = await getFileForLocale(localeName);

    if (!await translationFile.exists()) {
      return false;
    }

    // don't check file's age
    if (!ignoreCacheDuration) {
      var difference =
      DateTime.now().difference(await translationFile.lastModified());

      if (difference > (localCacheDuration)) {
        return false;
      }
    }

    return true;
  }

  Future<String> loadFromLocalFile(String localeName) async {
    return await (await getFileForLocale(localeName)).readAsString();
  }

  Future<void> saveTranslation(String localeName, String content) async {
    try{
      var file = File(await getFilenameForLocale(localeName));
      await file.create(recursive: true);
      return print('saved');
    }catch(e)
    {
     print("error creating file $e");
    }

  }

  Future<String> get _localPath async {
    final directory = await paths.getTemporaryDirectory();

    return directory.path;
  }

  Future<String> getFilenameForLocale(String localeName) async {
    return '${await _localPath}/translations/$localeName.json';
  }

  Future<File> getFileForLocale(String localeName) async {
    return File(await getFilenameForLocale(localeName));
  }
}

main.dart file code

runApp((EasyLocalization(
        supportedLocales:[
        Locale('en', 'US'),
        Locale('es', 'ES')
         ],
        path: 'assets/translations',
        fallbackLocale: const Locale('en', 'US'),
        assetLoader:  SmartNetworkAssetLoader(
          assetsPath: 'assets/translations',
          localCacheDuration: const Duration(days: 1),
          localeUrl: (String localeName) => Constants.appLangUrl,
          timeout: const Duration(seconds: 30),
        ),
        child: const MyApp())));

Dropdown onchanged function.

DropdownButton<String>(
                              value: state.selectedValue,
                              onChanged: (newValue) async {
                                context.read<LoginBloc>().add(DropDownButtonClickedEvent(newValue!));
                                await changeLanguageHandler(context, newValue);
                              },
                              items: state.list
                                  ?.map<DropdownMenuItem<String>>((element) {
                                return DropdownMenuItem<String>(
                                  value: element,
                                  child: Text(
                                    element,
                                    style: const TextStyle(
                                      color: Colors.white,
                                      fontSize: 18,
                                      fontWeight: FontWeight.w400,
                                    ),
                                  ),
                                );
                              }).toList(),
                              icon: Image.asset(
                                "assets/images/arrowdown.png",
                                width: 16.w,
                                height: 7.h,
                              ),
                            ),

on dropdown click will call the below function.

Future<void> changeLanguageHandler( BuildContext context, String language) async {
            try {
      await LocalizationChecker.changeLanguage(context, language);
      setState(() {});
     
    } catch (error) {
      print("error $error");
    }
  }
class LocalizationChecker {
  static changeLanguage(BuildContext context, String language) {
    // Locale? currentLocal = EasyLocalization.of(context)!.currentLocale;
    print("inside localization");
    if (language == 'Spanish') {
      EasyLocalization.of(context)!.setLocale(const Locale('es', 'ES'));
    } else {
      EasyLocalization.of(context)!.setLocale(const Locale('en', 'US'));
    }
  }
}

here my main issue is translation is not working consistently when we are using through api call that is (SmartAssetLoader class), why it happens is there any issue on my smartassetloader class or any libray issue. Here for changing the translation through api call we are using the library called easy_localization_loader 2.0.1 and easy_localization 3.0.3 please help i am new to this.

1

There are 1 best solutions below

5
VonC On

Your SmartNetworkAssetLoader (from easy_localization AssetLoader) checks for local translations, then network, and again local before finally trying the assets(!). That complex logic may cause issues if any step has unexpected behavior. Especially, the network loading part might be failing silently: network issues could cause inconsistent loading of translations. If the network request fails or times out, the loader will fall back to local or asset-based translations.

Plus, your saveTranslation function creates a file but does not actually write the content to it. That means your local translations may never actually be saved.

Modify the SmartNetworkAssetLoader class to include simplifying the loading logic, enhancing error handling and logging, checking network reliability, and fixing the file saving logic.

class SmartNetworkAssetLoader extends AssetLoader {
  final Function localeUrl;
  final Duration timeout;
  final String assetsPath;
  final Duration localCacheDuration;

  SmartNetworkAssetLoader({
    required this.localeUrl,
    this.timeout = const Duration(seconds: 30),
    required this.assetsPath,
    this.localCacheDuration = const Duration(days: 1),
  });

  @override
  Future<Map<String, dynamic>> load(String localePath, Locale locale) async {
    var string = '';
    var localeString = locale.toString();

    try {
      // Check local cache first
      if (await localTranslationExists(localeString)) {
        string = await loadFromLocalFile(localeString);
      }

      // If not in local cache or cache is outdated, try to load from network
      if (string.isEmpty && await isInternetConnectionAvailable()) {
        string = await loadFromNetwork(localeString);
      }

      // If still not loaded, use assets as a fallback
      if (string.isEmpty) {
        string = await loadFromAssets(localeString);
      }

      // Parse and return the JSON
      return json.decode(string);
    } catch (e) {
      print("Error loading translation: $e");
      // Fallback to loading from assets in case of any error
      return json.decode(await loadFromAssets(localeString));
    }
  }

  Future<String> loadFromAssets(String localeString) async {
    String convertedLocale = localeString.replaceAll('_', '-');
    return await rootBundle.loadString('$assetsPath/$convertedLocale.json');
  }

  // Rest of the methods remain the same
}

// Make sure the saveTranslation method writes the content to the file
Future<void> saveTranslation(String localeName, String content) async {
  try {
    var file = File(await getFilenameForLocale(localeName));
    await file.create(recursive: true);
    await file.writeAsString(content); // Write content to the file
    print('Translation saved for $localeName');
  } catch (e) {
    print("Error saving translation for $localeName: $e");
  }
}

The function now checks for local translation first, then network, and finally falls back to assets. That simplifies the flow. The loader now catches any exceptions during the loading process, logs them, and falls back to assets if needed.

The existing logic for network checks remains the same, but any failure in network loading leads to an immediate fallback to assets.
And the saveTranslation function now correctly writes the content to the file.


The real problem is a delay in loading the data in API that why is sometimes it doesn't translate the data, so can we add a loading indicator in switching between the languages?

You could indeed add a loading indicator when switching between languages in your Flutter app. You would update your UI to show a loading spinner or similar indicator while the app is fetching and applying the new translations.

First, you need a way to track whether your app is currently loading new language data. You can use a state management solution (like setState, Provider, Bloc, etc.) to manage a boolean variable that represents the loading state.
Modify the function responsible for switching languages to toggle the loading state before and after the language change process. And update your UI to show a loading indicator (like CircularProgressIndicator) when the app is in the loading state.

Using a simple setState approach:

class MyHomePage extends StatefulWidget {
  // 
}

class _MyHomePageState extends State<MyHomePage> {
  bool _isLoading = false;

  Future<void> changeLanguage(String languageCode) async {
    try {
      setState(() => _isLoading = true);
      // Simulate network delay
      await Future.delayed(Duration(seconds: 2));
      // Language switching logic
      await context.setLocale(Locale(languageCode));
    } finally {
      setState(() => _isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("App Title"),
      ),
      body: _isLoading
          ? Center(child: CircularProgressIndicator())
          : Column(
              // Your main content
            ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => changeLanguage('es'), // Example language switch
        tooltip: 'Change Language',
        child: Icon(Icons.language),
      ),
    );
  }
}

_isLoading is used to track the loading state.
The changeLanguage function updates _isLoading before starting and after completing the language switch.
The build method checks _isLoading and displays a CircularProgressIndicator when loading is in progress.

That would make sure users are aware that the app is working on something and improves the overall user experience, especially when dealing with network delays.


in my code finally calling instantly, so the loading indicator is not visible, but the translation takes time.

Would it be possible to add a progress indicator inside the SmartNetworkAssetLoader class? Like this:

LoadingIndicator.buildShowDialog(context, '');

Because I am using an overlay indicator, for that I need context.
Is it possible to add the above code inside the SmartNetworkAssetLoader

Inside the SmartNetworkAssetLoader? No.

It would not be the best practice because the AssetLoader class in the easy_localization package is designed for loading assets and should not be responsible for UI-related tasks like showing loading indicators.
Mixing data fetching logic with UI handling can lead to code that is harder to maintain and debug.

However, you can achieve the desired behavior by handling the loading indicator at the UI level, where you have access to the BuildContext.

Before invoking the language change or data loading process, show the loading indicator. Perform the language change operation and wait for it to complete. Since this operation is asynchronous, you can await it in the UI layer. Once the language change and data loading are complete, hide the loading indicator.

Your code would be:

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  Future<void> changeLanguage(String languageCode) async {
    try {
      // Show the loading indicator
      LoadingIndicator.buildShowDialog(context, '');

      // Change language and load assets
      await context.setLocale(Locale(languageCode));
      await loadTranslationData(languageCode);

      // Hide the loading indicator
      Navigator.of(context, rootNavigator: true).pop();
    } catch (e) {
      print("Error changing language: $e");
    }
  }

  Future<void> loadTranslationData(String languageCode) async {
    // Your logic to load translation data
  }

  @override
  Widget build(BuildContext context) {
    // Your widget build method
  }
}

changeLanguage is responsible for showing the loading indicator, performing the language change, and then hiding the indicator.
The actual loading of translation data is abstracted in loadTranslationData, which can be your logic to fetch and apply translations.

That way, you maintain a separation of concerns: SmartNetworkAssetLoader focuses solely on loading assets, while your widget manages the UI state, including showing and hiding loading indicators.