df_config loads configuration data (YAML, JSON, JSONC, CSV) and exposes it through a tiny placeholder-substitution API. It powers df_localization, but is useful any time you want a config file that can reference itself and accept runtime overrides.
A config can reference its own values — the resolution happens at load time, so by the time you query a key you get the fully-substituted string. Combined with runtime arg overrides, the same file works for both static defaults and dynamic templating.
# assets/translations/en-us.yaml
app:
name: AcmeApp
tagline: "Welcome to {{app.name}}" # references a sibling key
cta: "Get started with {{app.name}} {action}" # `{action}` is filled at call-time'{{Get started||app.cta}}'.tr(args: {'action': 'now!'})
// → "Get started with AcmeApp now!"{{ default || key }}— primary form. Looks upkeyin the active config; if missing, returnsdefault.{ default | key }— secondary form. Resolved in a second pass againstargs, so single-brace tokens are reserved for runtime values (think{name}).- Keys can be dotted paths (
app.name,tags.0) to reach into nested maps and lists. - Lookups are case-insensitive by default — change via
PatternSettings(caseSensitive: true).
import 'package:df_config/df_config.dart';
void main() async {
await TranslationManager.setConfig(
await FileConfig.read(
ref: ConfigFileRef(
ref: 'en',
type: ConfigFileType.YAML,
read: () async => '''
app:
name: AcmeApp
tagline: "Welcome to {{app.name}}"
cta: "Get started with {{app.name}} {action}"
''',
),
),
);
print('Hi||app.tagline'.tr());
// → "Welcome to AcmeApp"
print('Default||app.cta'.tr(args: {'action': 'today!'}));
// → "Get started with AcmeApp today!"
// Unknown key → falls back to the `default` text before `||`.
print('Just a default||missing'.tr());
// → "Just a default"
}import 'package:df_config/df_config.dart';
void main() async {
// Create a reader for YAML translation files in `assets/translations/`.
final reader = TranslationFileReader(
translationsDirPath: const ['assets', 'translations'],
fileType: ConfigFileType.YAML,
fileReader: (filePath) async {
// In Flutter this would be `rootBundle.loadString(filePath)`.
return fileData[filePath] ?? '';
},
);
// German.
await reader.read('de-de');
print('Example App||app.title'.tr()); // → "BEISPIEL!!!"
// Spanish.
await reader.read('es-es');
print('Example App||app.title'.tr()); // → "EJEMPLO!!!"
// English (with a self-reference and a runtime `{additional}` slot).
await reader.read('en-us');
print('Example App||app.title'.tr(
args: {'additional': 'of the app!'},
));
// → "ENGLISH X EXAMPLE!!! of the app!"
// Unknown locale → primary pass falls back to the default text,
// secondary pass still substitutes args.
await reader.read('qwerty');
print('Hey {{Example {App|app}||app.title}} dude'.tr(
args: {'app': 'Application'},
));
// → "Hey Example Application dude"
}
const fileData = {
'assets/translations/de-de.yaml': '''
app:
title: BEISPIEL!!!
''',
'assets/translations/es-es.yaml': '''
app:
title: EJEMPLO!!!
''',
'assets/translations/en-us.yaml': '''
example: X
app:
example: EXAMPLE
# You can reference other keys within this file, and use {placeholders}
# to insert values at runtime.
title: "ENGLISH {{example}} {{app.example}}!!! {additional}"
''',
};If something inside the substitution layer throws — a buggy mapper, a pathological input, a missing config — .tr() returns the original string verbatim. Translation is best-effort by design; you should never see a .tr() call crash your UI.
To rebuild the widget tree when the language changes, wrap your MaterialApp in a ValueListenableBuilder (or use the convenience widgets in df_localization) and rebuild on every TranslationManager.setConfig(...) call.
🔍 For more information, refer to the API reference.
This is an open-source project, and we warmly welcome contributions from everyone, regardless of experience level. Whether you're a seasoned developer or just starting out, contributing to this project is a fantastic way to learn, share your knowledge, and make a meaningful impact on the community.
- Find us on Discord: Feel free to ask questions and engage with the community here: https://discord.gg/gEQ8y2nfyX.
- Share your ideas: Every perspective matters, and your ideas can spark innovation.
- Help others: Engage with other users by offering advice, solutions, or troubleshooting assistance.
- Report bugs: Help us identify and fix issues to make the project more robust.
- Suggest improvements or new features: Your ideas can help shape the future of the project.
- Help clarify documentation: Good documentation is key to accessibility. You can make it easier for others to get started by improving or expanding our documentation.
- Write articles: Share your knowledge by writing tutorials, guides, or blog posts about your experiences with the project. It's a great way to contribute and help others learn.
No matter how you choose to contribute, your involvement is greatly appreciated and valued!
If you're enjoying this package and find it valuable, consider showing your appreciation with a small donation. Every bit helps in supporting future development. You can donate here: https://www.buymeacoffee.com/dev_cetera
This project is released under the MIT License. See LICENSE for more information.