Skip to content

Conversation

@jonasbadstuebner
Copy link

@jonasbadstuebner jonasbadstuebner commented Mar 16, 2023

I got an error like this:

Flutter Error:  type '_Map<String, dynamic>' is not a subtype of type 'String?'

from Translations.get.
I had a JSON for translation like

{
  "error": {
    "unknown": "An unknown error occurred",
    "unauthenticated": "You need to log in first"
  }
}

etc.

My code was trying to translate like tr(errorString) where errorString was "error" and this led to the lookup in lib/src/translations.dart#L16 to try and put the resulting Map (

{
  "unknown": "An unknown error occurred",
  "unauthenticated": "You need to log in first"
}

) into a String?.

This PR fixes the problem and just gives [🌎 Easy Localization] [WARNING] Localization key [error] not found as expected.

I did not check in any other places, if this could happen. If an error occurs somewhere else, I will open a new PR.

By the way, when will you release the next time? The last release on pub.dev was 10 months ago...?

Summary by CodeRabbit

  • Bug Fixes
    • Hardened translation retrieval to return only text values from top-level entries, avoiding non-string data appearing in the UI. This results in more reliable localization output and reduces edge-case rendering issues when translation maps contain mixed types. Nested key lookups and caching behavior remain unchanged, preserving existing behavior while improving correctness of returned strings. No user-facing settings or workflows were altered.

@bw-flagship
Copy link
Collaborator

@DrBu7cher Thanks for this PR, a very good idea! Maybe we should add a dedicated warning-log in this case, so the user knows what's wrong? Anyways, it would be important to have a unittest covering this, so it's not destroyed by a another change.

/// If we failed to find the key as a nested key, then fall back
/// to looking it up like normal.
returnValue ??= _translations?[key];
var maybeValue = _translations?[key];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we do the lookup all the time, even if it's not needed because returnValue != null

/// to looking it up like normal.
returnValue ??= _translations?[key];
var maybeValue = _translations?[key];
if (maybeValue is String) returnValue ??= maybeValue;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need a unittest for the new behavior

@jonasbadstuebner
Copy link
Author

Good ideas, I will come back to that.

@coderabbitai
Copy link

coderabbitai bot commented Aug 27, 2025

Walkthrough

The Translations.get method now guards the top-level fallback lookup: it retrieves _translations?[key] into a temporary and assigns it only if the value is a String. Nested-key lookup and caching behavior are unchanged. No public API signatures were modified.

Changes

Cohort / File(s) Summary
Translations fallback guard
lib/src/translations.dart
Added a type check when reading from the top-level _translations map: only assign fallback if the value is a String. Removed direct returnValue ??= _translations?[key] in favor of a guarded assignment. No changes to nested lookup or caching logic.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Caller
  participant T as Translations.get
  participant M as _translations

  C->>T: get(key)
  alt key contains dot (nested)
    T->>T: resolve nested value
    alt found String
      T-->>C: return value
    else not found
      T->>M: maybeValue = _translations?[key]
      alt maybeValue is String
        T-->>C: return maybeValue
      else not String / null
        T-->>C: return null
      end
    end
  else non-nested
    T->>M: maybeValue = _translations?[key]
    alt maybeValue is String
      T-->>C: return maybeValue
    else not String / null
      T-->>C: return null
    end
  end
  note over T: Caching behavior unchanged
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

I hop through maps with keys so bright,
Guarding strings by soft moonlight—
If value’s true and typed just right,
I tuck it in, a snug delight.
No more odd carrots in the bin—
Only strings may travel in. 🥕🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/src/translations.dart (1)

36-43: Prevent runtime type errors: only cache/return Strings from nested lookup.

Currently, cacheNestedKey(String value) can receive a non-String (Map/num/bool), causing a runtime type error. Return null for non-String leaves to honor String? API and cache only valid Strings.

-    /// If we found the value, cache it. If the value is null then
-    /// we're not going to cache it, and returning null instead.
-    if (value != null) {
-      cacheNestedKey(key, value);
-    }
-
-    return value;
+    /// Cache and return only String values; treat non-String leaves as not found.
+    if (value is String) {
+      cacheNestedKey(key, value);
+      return value;
+    }
+    return null;
♻️ Duplicate comments (2)
lib/src/translations.dart (2)

16-17: Avoid unnecessary map lookup when nested lookup already succeeded.

Do the top-level lookup only if returnValue == null. This addresses bw-flagship’s note while keeping the new type guard.

-    var maybeValue = _translations?[key];
-    if (maybeValue is String) returnValue ??= maybeValue;
+    if (returnValue == null) {
+      final v = _translations?[key];
+      if (v is String) {
+        returnValue = v;
+      }
+    }

16-17: Add unit tests for the new behavior (top-level non-String and nested non-String).

Covers the regression mentioned by bw-flagship and this PR’s fix: returning null (plus warning) instead of throwing on maps; also covers nested non-String leaves.

Proposed test (new file: test/translations_get_test.dart):

import 'package:test/test.dart';
import 'package:easy_localization/src/translations.dart';

void main() {
  group('Translations.get', () {
    test('returns null (and warns) when top-level value is a map', () {
      final t = Translations({
        'error': {
          'unknown': 'An unknown error occurred',
          'unauthenticated': 'You need to log in first',
        },
        'literal': 'ok',
      });

      expect(t.get('error'), isNull); // non-leaf key
      expect(t.get('literal'), 'ok');
      expect(t.get('error.unknown'), 'An unknown error occurred');
    });

    test('nested non-String returns null and is not cached', () {
      final t = Translations({
        'a': {
          'b': {}, // non-leaf at the end
        },
      });

      expect(t.get('a.b'), isNull);
      expect(t.get('a.b.c'), isNull); // should not crash or return a non-String
    });
  });
}
🧹 Nitpick comments (3)
lib/src/translations.dart (3)

16-19: Add a targeted warning when the top-level value is a non-String (object).

Users get a clearer hint that they referenced a non-leaf key (e.g., "error" vs "error.unknown"). Keep this behind the same conditional to avoid extra lookups.

-    if (returnValue == null) {
-      final v = _translations?[key];
-      if (v is String) {
-        returnValue = v;
-      }
-    }
+    if (returnValue == null) {
+      final v = _translations?[key];
+      if (v is String) {
+        returnValue = v;
+      } else if (v != null) {
+        // Consider using the project's logger instead of print if available.
+        print('[🌎 Easy Localization] [WARNING] Localization key [$key] points to an object; use a dotted path like "$key.<leaf>".');
+      }
+    }

32-34: Minor: stop traversing once the path can’t continue.

Short-circuit the loop when value is no longer a map. Saves pointless iterations and clarifies intent.

-    for (var i = 1; i < keys.length; i++) {
-      if (value is Map<String, dynamic>) value = value[keys[i]];
-    }
+    for (var i = 1; i < keys.length && value is Map<String, dynamic>; i++) {
+      value = (value as Map<String, dynamic>)[keys[i]];
+    }

59-60: Null-safety: avoid ! on _translations in isNestedKey.

Prevents a possible NPE if _translations is null.

-  bool isNestedKey(String key) =>
-      !_translations!.containsKey(key) && key.contains('.');
+  bool isNestedKey(String key) =>
+      (_translations != null && !_translations!.containsKey(key)) && key.contains('.');
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c818108 and c6da10b.

📒 Files selected for processing (1)
  • lib/src/translations.dart (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Codacy Static Code Analysis

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants