Skip to content
ExponaBLOG
FLUTTER · DAY 071 / 100

Fonts for Tamil and Sinhala: what developers should know

Tamil and Sinhala text breaks layouts in ways English never will. Test with real scripts, not lorem ipsum.

Expona Admin7 min read

Fonts for Tamil and Sinhala

Building apps for South Asian markets means moving beyond ASCII and embracing scripts that have complex shaping rules, variable character widths, and specific rendering requirements. If you're shipping a Flutter app to Tamil Nadu or Sri Lanka without proper font support, you'll face clipped text, misaligned baselines, and frustrated users staring at broken characters. Today we're tackling how to bundle fonts that actually work with Tamil and Sinhala, validate rendering at the UI level, and test with real translated content instead of placeholder text.

Bundle Fonts That Support Your Scripts

The first mistake most developers make is assuming the system font will handle any script. It won't. Tamil and Sinhala require fonts with complete glyph coverage and proper OpenType features for ligatures and consonant clusters. You can't rely on what's pre-installed on the device—you need to bundle fonts explicitly.

Start by identifying a font that covers your target scripts. I typically use Noto Sans Tamil, Noto Sans Sinhala, or Mukta (which covers both). Download the font file (.ttf or .otf) and place it in your Flutter project:

dart

// Directory structure
assets/
  fonts/
    NotoSansTamil-Regular.ttf
    NotoSansTamil-Bold.ttf
    NotoSansSinhala-Regular.ttf
    NotoSansSinhala-Bold.ttf

Then declare these fonts in your pubspec.yaml:

dart

flutter:
  fonts:
    - family: NotoSansTamil
      fonts:
        - asset: assets/fonts/NotoSansTamil-Regular.ttf
          weight: 400
        - asset: assets/fonts/NotoSansTamil-Bold.ttf
          weight: 700
    - family: NotoSansSinhala
      fonts:
        - asset: assets/fonts/NotoSansSinhala-Regular.ttf
          weight: 400
        - asset: assets/fonts/NotoSansSinhala-Bold.ttf
          weight: 700

Now apply the font to your text widgets. The key is to use a font family that matches your content's script:

dart

Text(
  'வணக்கம்', // Tamil: "Vanakkam" (Hello)
  style: TextStyle(
    fontFamily: 'NotoSansTamil',
    fontSize: 18,
    fontWeight: FontWeight.w400,
  ),
)

Text(
  'ඔබට සුබ දිනක්', // Sinhala: "Obata suba dinaka" (Have a good day)
  style: TextStyle(
    fontFamily: 'NotoSansSinhala',
    fontSize: 18,
    fontWeight: FontWeight.w400,
  ),
)

For multilingual apps, create a helper function that selects the font based on detected language or locale:

dart

String getFontFamily(Locale locale) {
  if (locale.languageCode == 'ta') {
    return 'NotoSansTamil';
  } else if (locale.languageCode == 'si') {
    return 'NotoSansSinhala';
  }
  return 'Roboto'; // Fallback
}

// Usage
Text(
  translatedString,
  style: TextStyle(
    fontFamily: getFontFamily(Locale.current),
    fontSize: 16,
  ),
)

Verify font files are recognized by Flutter. Run flutter pub get and check the build output for warnings. If a font isn't loading, double-check the asset path spelling and ensure the pubspec.yaml indentation is correct.

Check Line Height and Clipping

Tamil and Sinhala scripts have different metrics than Latin text. Many characters sit above or below the baseline, and diacritical marks (vowel modifiers) stack vertically. If you don't adjust line height, you'll see text clipping or overlapping in multiline scenarios.

Here's what goes wrong: you set height: 1.0 (the default), and a Tamil word with a vowel modifier gets cut off at the top or bottom. The character itself fits, but the combining mark doesn't.

Always increase line height for these scripts. Start with 1.3 and adjust based on testing:

dart

Text(
  'தமிழ் மொழி', // Tamil: "Tamil language"
  style: TextStyle(
    fontFamily: 'NotoSansTamil',
    fontSize: 16,
    height: 1.5, // Increased from default 1.0
  ),
)

For paragraph-level text, use TextStyle within a Paragraph or wrap content in a container with explicit padding:

dart

Container(
  padding: EdgeInsets.symmetric(vertical: 8.0),
  child: Text(
    'ශ්‍රී ලංකා සිංහල ඉංග්‍රීසි', // Sinhala: Mixed text with ligatures
    style: TextStyle(
      fontFamily: 'NotoSansSinhala',
      fontSize: 16,
      height: 1.6,
    ),
  ),
)

Test on real devices and emulators at various font sizes. I've seen clipping that only appears at sizes 18 and above, or only in certain device orientations. Use Flutter DevTools or print debug output to confirm the text bounding box:

dart

final textPainter = TextPainter(
  text: TextSpan(
    text: 'සිංහල',
    style: TextStyle(fontFamily: 'NotoSansSinhala', fontSize: 16),
  ),
  textDirection: TextDirection.ltr,
);
textPainter.layout();
print('Width: ${textPainter.width}, Height: ${textPainter.height}');

Test Long Translated Strings

Lorem ipsum doesn't reveal layout issues with real scripts. A Tamil or Sinhala sentence might be 20% longer than its English equivalent, forcing word wrapping and affecting button sizes, card heights, and alignment.

Collect actual translated strings early, ideally from native speakers or translators. Don't use machine translation for testing—you need grammatically correct, natural phrasing to spot real problems.

Create a dedicated test file with representative strings:

dart

final tamileStrings = {
  'hello': 'வணக்கம்',
  'welcome': 'உங்களை வரவேற்கிறோம்',
  'confirm_action': 'நீங்கள் இந்த செயலை உறுதிப்படுத்த விரும்புகிறீர்களா?',
  'error_long': 'இந்த வினவுக்கு பதிலளிக்க முடியவில்லை. பின்னர் மீண்டும் முயற்சிக்கவும்.',
};

final sinhalaStrings = {
  'hello': 'ආයුබෝවන්',
  'welcome': 'ඔබනට ස්වාගතයි',
  'confirm_action': 'ඔබ මෙම ක්‍රියාව තහවුරු කිරීමට ඉතා ඉතා බලාපොරොත්තුවෙතින් ඉන්නේද?',
  'error_long': 'මෙම ඉල්ලීමට පිළිතුරු දිමට නොහැකි විය. පසුව නැවත උත්සාහ කරන්න.',
};

Now render these in your UI and check for overflow, text clipping, and button expansion:

dart

class LocalizationTestScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Localization Test')),
      body: SingleChildScrollView(
        child: Column(
          children: [
            _buildTestCard('Tamil', tamileStrings, 'NotoSansTamil'),
            _buildTestCard('Sinhala', sinhalaStrings, 'NotoSansSinhala'),
          ],
        ),
      ),
    );
  }

  Widget _buildTestCard(
    String title,
    Map strings,
    String fontFamily,
  ) {
    return Card(
      margin: EdgeInsets.all(16),
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              title,
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 12),
            ...strings.entries.map((e) {
              return Padding(
                padding: EdgeInsets.symmetric(vertical: 8),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      e.key,
                      style: TextStyle(fontSize: 12, color: Colors.grey),
                    ),
                    Text(
                      e.value,
                      style: TextStyle(
                        fontFamily: fontFamily,
                        fontSize: 16,
                        height: 1.5,
                      ),
                    ),
                  ],
                ),
              );
            }).toList(),
          ],
        ),
      ),
    );
  }
}

Pay special attention to forms and buttons. A label that says "Next" in English might overflow a 300-pixel button when translated to Tamil. Test all user-facing strings in your actual UI components, not in a static text widget.

Support RTL Where Needed

Sinhala is written left-to-right, so you don't need RTL support for it. However, if your app serves Arabic, Urdu, or Hebrew speakers alongside Sinhala and Tamil, you'll need conditional RTL handling. Even within Tamil and Sinhala content, numbers and Latin script may appear and should flow correctly.

Flutter handles text direction per widget. Set TextDirection explicitly if needed, or rely on Directionality at the app level:

dart

Directionality(
  textDirection: TextDirection.ltr, // Tamil and Sinhala are LTR
  child: MyApp(),
)

If you support mixed scripts or RTL languages, use a helper to determine direction dynamically:

dart

TextDirection getTextDirection(String languageCode) {
  if (['ar', 'ur', 'he'].contains(languageCode)) {
    return TextDirection.rtl;
  }
  return TextDirection.ltr;
}

// Usage
Directionality(
  textDirection: getTextDirection(locale.languageCode),
  child: MyApp(),
)

For individual widgets containing mixed content (e.g., "Price: ৳500" in Sinhala), Flutter's RichText with multiple TextSpan objects handles bidirectional text reasonably well, but test thoroughly on device.

Common Mistakes

  • Not bundling fonts: Relying on system fonts means your app breaks on devices without Tamil or Sinhala support. Always bundle.
  • Using default line height: Line height 1.0 is too tight for scripts with diacriticals. Start with 1.5 and refine.
  • Testing only with placeholder text: Lorem ipsum or dummy English strings hide real layout issues. Use genuine translated content early.
  • Ignoring font weight variants: If your design uses bold, ensure you've bundled a bold font file and declared it with the correct weight in pubspec.yaml.
  • Assuming a single font family works for all scripts: While Mukta covers both Tamil and Sinhala, most fonts specialize. Verify glyph coverage with a tool like Glyphs or FontForge before committing.
  • Not testing on actual devices: Emulators can render fonts differently. Test on iOS and Android hardware with real locale settings.

Key Takeaways

  • Bundle dedicated font files for Tamil and Sinhala; don't rely on system fonts.
  • Increase line height to 1.5 or higher to prevent clipping of diacritical marks and vowel modifiers.
  • Test UI layouts with actual translated strings, not placeholder text, to catch overflow and alignment issues early.
  • Use TextDirection.ltr for Tamil and Sinhala; add conditional RTL support only if your app targets RTL scripts.
  • Verify font rendering on real devices and multiple locales before shipping to production.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles