Flutter Table Localization: Grid Layouts for Multilingual Apps
Table is a Flutter widget that displays children in a grid of rows and columns with configurable column widths. In multilingual applications, Table is essential for presenting translated data in structured grids, adapting column widths for translations of varying lengths, aligning content correctly in RTL layouts, and building comparison tables and specification sheets with localized labels.
Understanding Table in Localization Context
Table renders children in a fixed grid where each row has the same number of cells and column widths can be controlled independently. For multilingual apps, this enables:
- Translated header and data cells in a structured grid layout
- Adaptive column widths that accommodate different translation lengths
- RTL-aware cell alignment that reverses column order automatically
- Specification tables with localized labels and values
Why Table Matters for Multilingual Apps
Table provides:
- Fixed grid layout: Consistent rows and columns for translated data
- Column width control:
FlexColumnWidth,FixedColumnWidth, andIntrinsicColumnWidthadapt to translation length - Cell alignment: Per-cell vertical alignment works across all text directions
- Border customization: Consistent borders regardless of translation length
Basic Table Implementation
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedTableExample extends StatelessWidget {
const LocalizedTableExample({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.specificationTitle)),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Table(
border: TableBorder.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
columnWidths: const {
0: FlexColumnWidth(2),
1: FlexColumnWidth(3),
},
children: [
TableRow(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
),
children: [
_HeaderCell(text: l10n.propertyHeader),
_HeaderCell(text: l10n.valueHeader),
],
),
_buildRow(l10n.dimensionsLabel, l10n.dimensionsValue),
_buildRow(l10n.weightLabel, l10n.weightValue),
_buildRow(l10n.materialLabel, l10n.materialValue),
_buildRow(l10n.colorLabel, l10n.colorValue),
_buildRow(l10n.warrantyLabel, l10n.warrantyValue),
],
),
),
);
}
TableRow _buildRow(String label, String value) {
return TableRow(
children: [
_DataCell(text: label, isBold: true),
_DataCell(text: value),
],
);
}
}
class _HeaderCell extends StatelessWidget {
final String text;
const _HeaderCell({required this.text});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(12),
child: Text(
text,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
),
),
);
}
}
class _DataCell extends StatelessWidget {
final String text;
final bool isBold;
const _DataCell({required this.text, this.isBold = false});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(12),
child: Text(
text,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: isBold ? FontWeight.w600 : null,
),
),
);
}
}
Advanced Table Patterns for Localization
Comparison Table with Localized Features
Feature comparison tables with translated feature names and plan labels.
class LocalizedComparisonTable extends StatelessWidget {
const LocalizedComparisonTable({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Table(
border: TableBorder.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
columnWidths: const {
0: FlexColumnWidth(3),
1: FlexColumnWidth(2),
2: FlexColumnWidth(2),
3: FlexColumnWidth(2),
},
children: [
TableRow(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
),
children: [
_HeaderCell(text: l10n.featureHeader),
_HeaderCell(text: l10n.basicPlanLabel),
_HeaderCell(text: l10n.proPlanLabel),
_HeaderCell(text: l10n.enterprisePlanLabel),
],
),
_featureRow(context, l10n.featureStorage, '5 GB', '50 GB',
l10n.unlimitedLabel),
_featureRow(context, l10n.featureUsers, '1', '10',
l10n.unlimitedLabel),
_featureRow(context, l10n.featureSupport,
l10n.emailSupportLabel, l10n.prioritySupportLabel,
l10n.dedicatedSupportLabel),
_featureRow(context, l10n.featureAnalytics,
l10n.basicLabel, l10n.advancedLabel, l10n.customLabel),
],
),
);
}
TableRow _featureRow(BuildContext context, String feature,
String basic, String pro, String enterprise) {
return TableRow(
children: [
_DataCell(text: feature, isBold: true),
_DataCell(text: basic),
_DataCell(text: pro),
_DataCell(text: enterprise),
],
);
}
}
Schedule Table with Localized Time Slots
A timetable layout with translated day names and event descriptions.
class LocalizedScheduleTable extends StatelessWidget {
const LocalizedScheduleTable({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final days = [
l10n.mondayShort,
l10n.tuesdayShort,
l10n.wednesdayShort,
l10n.thursdayShort,
l10n.fridayShort,
];
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Table(
border: TableBorder.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
defaultColumnWidth: const FixedColumnWidth(120),
columnWidths: const {
0: FixedColumnWidth(80),
},
children: [
TableRow(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
),
children: [
_HeaderCell(text: l10n.timeHeader),
...days.map((day) => _HeaderCell(text: day)),
],
),
_timeRow(context, '09:00', [
l10n.meetingEvent, '', l10n.workshopEvent, '', l10n.reviewEvent,
]),
_timeRow(context, '11:00', [
'', l10n.presentationEvent, '', l10n.trainingEvent, '',
]),
_timeRow(context, '14:00', [
l10n.lunchEvent, l10n.lunchEvent, l10n.lunchEvent,
l10n.lunchEvent, l10n.lunchEvent,
]),
],
),
),
);
}
TableRow _timeRow(BuildContext context, String time, List<String> events) {
return TableRow(
children: [
_DataCell(text: time, isBold: true),
...events.map((event) => _DataCell(text: event)),
],
);
}
}
Locale-Aware Column Widths
Adjust column proportions based on locale to accommodate verbose translations.
class AdaptiveColumnTable extends StatelessWidget {
const AdaptiveColumnTable({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final locale = Localizations.localeOf(context);
final isVerbose = ['de', 'fi', 'hu', 'nl'].contains(locale.languageCode);
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Table(
border: TableBorder.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
columnWidths: {
0: FlexColumnWidth(isVerbose ? 3 : 2),
1: FlexColumnWidth(isVerbose ? 2 : 3),
},
children: [
TableRow(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
),
children: [
_HeaderCell(text: l10n.settingLabel),
_HeaderCell(text: l10n.descriptionLabel),
],
),
_buildRow(l10n.languageSettingLabel, l10n.languageSettingDescription),
_buildRow(l10n.themeSettingLabel, l10n.themeSettingDescription),
_buildRow(l10n.notificationSettingLabel,
l10n.notificationSettingDescription),
],
),
);
}
TableRow _buildRow(String label, String description) {
return TableRow(
children: [
_DataCell(text: label, isBold: true),
_DataCell(text: description),
],
);
}
}
RTL Support and Bidirectional Layouts
Table automatically reverses column order in RTL layouts. Cell content aligns according to the active directionality.
class BidirectionalTable extends StatelessWidget {
const BidirectionalTable({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Padding(
padding: const EdgeInsetsDirectional.all(16),
child: Table(
border: TableBorder.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
children: [
TableRow(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
),
children: [
_HeaderCell(text: l10n.nameHeader),
_HeaderCell(text: l10n.roleHeader),
_HeaderCell(text: l10n.statusHeader),
],
),
TableRow(children: [
_DataCell(text: l10n.sampleUserName),
_DataCell(text: l10n.adminRole),
_DataCell(text: l10n.activeStatus),
]),
TableRow(children: [
_DataCell(text: l10n.sampleUserName2),
_DataCell(text: l10n.editorRole),
_DataCell(text: l10n.inactiveStatus),
]),
],
),
);
}
}
Testing Table Localization
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
void main() {
Widget buildTestWidget({Locale locale = const Locale('en')}) {
return MaterialApp(
locale: locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const LocalizedTableExample(),
);
}
testWidgets('Table renders localized cells', (tester) async {
await tester.pumpWidget(buildTestWidget());
await tester.pumpAndSettle();
expect(find.byType(Table), findsOneWidget);
});
testWidgets('Table works in RTL', (tester) async {
await tester.pumpWidget(buildTestWidget(locale: const Locale('ar')));
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
});
}
Best Practices
Use
FlexColumnWidthfor proportional columns that adapt to available space, adjusting ratios for verbose languages.Use
IntrinsicColumnWidthwhen columns should size to their widest translated content automatically.Wrap horizontal tables in
SingleChildScrollViewwithAxis.horizontalwhen translations may make the table wider than the screen.Style header rows with a distinct background color and bold translated text to visually separate them from data rows.
Use
EdgeInsetsDirectionalfor table cell padding so insets adapt correctly in RTL layouts.Test with verbose languages to verify column widths accommodate longer translations without clipping.
Conclusion
Table provides a fixed grid layout for structured data in Flutter. For multilingual apps, it handles translated headers and data cells with configurable column widths that can adapt to translation length. By using proportional column widths, locale-aware sizing, and RTL-aware alignment, you can build specification sheets, comparison tables, and schedules that display correctly across all supported languages.