Skip to content

Commit

Permalink
Added support for formatting from 0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
vanlooverenkoen committed Jun 22, 2019
1 parent 52e44de commit f9d5bd4
Show file tree
Hide file tree
Showing 12 changed files with 334 additions and 34 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [0.0.2] - 2019-02-12
### Added
-Support for arguments

### Fixed
-Dart formatting

## [0.0.2] - 2019-02-12
### Added
-Updated README.md
Expand Down
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,38 @@ flutter packages pub run locale_gen
```
pub run locale_gen
```

### Arguments

Arguments are supported as of 0.1.0

You can pass a String or a num to as an argument.

Formatting for String: %1$s
Formatting for num: %1$d

The number in between % and $ indicate the index of the argument. It is possible to place an argument in 1 language first but in another second:

ex (Grammatically incorrect but it makes my point):

```
nl '%1$s, ik woon in $2%s. Wist je dat niet?' => KOEN, ik woon in ANTWERPEN. Wist je dat niet?
fr 'I live in $2%s. You didn't knew that %1$s?" => I live in ANTWERP. You didn't knew that KOEN?
```

### Working on mac?

add this to you .bash_profile

```
flutterlocalegen(){
flutter packages get && flutter packages pub run locale_gen
}
```

now you can use the locale_gen with a single command.

```
flutterlocalegen
```
79 changes: 57 additions & 22 deletions bin/locale_gen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import 'dart:io';

import 'package:path/path.dart';

import 'src/case_util.dart';
import 'src/params.dart';
import 'src/translation_file_writer.dart';

final outputDir = join('lib', 'util', 'locale');
final assetsDir = join('assets', 'locale');
Expand All @@ -14,11 +14,14 @@ Params params;

Future<void> main(List<String> args) async {
final pubspecYaml = File(join(Directory.current.path, 'pubspec.yaml'));
if (!pubspecYaml.existsSync()) throw Exception('This program should be run from the root of a flutter/dart project');
if (!pubspecYaml.existsSync())
throw Exception(
'This program should be run from the root of a flutter/dart project');

await parsePubspec(pubspecYaml);

final defaultLocaleJson = File(join(Directory.current.path, assetsDir, '${params.defaultLanguage}.json'));
final defaultLocaleJson = File(join(
Directory.current.path, assetsDir, '${params.defaultLanguage}.json'));
if (!defaultLocaleJson.existsSync()) {
throw Exception('${defaultLocaleJson.path} does not exists');
}
Expand All @@ -43,40 +46,60 @@ void createLocalizationFile(File defaultLocaleJson) {
..writeln("import 'package:flutter/services.dart';")
..writeln("import 'package:flutter/widgets.dart';")
..writeln()
..writeln('//============================================================//')
..writeln(
'//============================================================//')
..writeln('//THIS FILE IS AUTO GENERATED. DO NOT EDIT//')
..writeln('//============================================================//')
..writeln(
'//============================================================//')
..writeln('class Localization {')
..writeln(' Map<dynamic, dynamic> _localisedValues;')
..writeln()
..writeln(' static Localization of(BuildContext context) => Localizations.of<Localization>(context, Localization);')
..writeln(
' static Localization of(BuildContext context) => Localizations.of<Localization>(context, Localization);')
..writeln(' ')
..writeln(' static Future<Localization> load(Locale locale) async {')
..writeln(' final localizations = Localization();')
..writeln(" print('Switching to \${locale.languageCode}');")
..writeln(" final jsonContent = await rootBundle.loadString('assets/locale/\${locale.languageCode}.json');")
..writeln(' final Map<String, dynamic> values = json.decode(jsonContent);')
..writeln(
" final jsonContent = await rootBundle.loadString('assets/locale/\${locale.languageCode}.json');")
..writeln(
' final Map<String, dynamic> values = json.decode(jsonContent);')
..writeln(' localizations._localisedValues = values;')
..writeln(' return localizations;')
..writeln(' }')
..writeln()
..writeln(' String _t(String key) {')
..writeln(' String _t(String key, {List<dynamic> args}) {')
..writeln(' try {')
..writeln(' final value = _localisedValues[key];')
..writeln(' String value = _localisedValues[key];')
..writeln(" if (value == null) return '⚠\$key⚠';")
..writeln(' if (args == null || args.isEmpty) return value;')
..writeln(
' args.asMap().forEach((index, arg) => value = _replaceWith(value, arg, index + 1));')
..writeln(' return value;')
..writeln(' } catch (e) {')
..writeln(" return '⚠\$key⚠';")
..writeln(' }')
..writeln(' }')
..writeln()
..writeln(' String _replaceWith(String value, arg, argIndex) {')
..writeln(' if (arg == null) return value;')
..writeln(' if (arg is String) {')
..writeln(" return value.replaceAll('%\$argIndex\\\$s', arg);")
..writeln(' } else if (arg is num) {')
..writeln(" return value.replaceAll('%\$argIndex\\\$d', '\$arg');")
..writeln(' }')
..writeln(' return value;')
..writeln(' }')
..writeln();
final content = defaultLocaleJson.readAsStringSync();
final translations = json.decode(content);
translations.forEach((key, value) => sb..writeln(" String get ${CaseUtil.getCamelcase(key)} => _t('$key');")..writeln());
translations.forEach(
(key, value) => FileWriter.buildTranslationFunction(sb, key, value));
sb.writeln('}');

// Write to file
final localizationFile = File(join(Directory.current.path, outputDir, 'localization.dart'));
final localizationFile =
File(join(Directory.current.path, outputDir, 'localization.dart'));
if (!localizationFile.existsSync()) {
print('localization.dart does not exists');
print('Creating localization.dart ...');
Expand All @@ -91,18 +114,27 @@ void createLocalizationDelegateFile() {
final sb = StringBuffer()
..writeln("import 'dart:async';")
..writeln()
..writeln("import 'package:${params.projectName}/util/locale/localization.dart';")
..writeln(
"import 'package:${params.projectName}/util/locale/localization.dart';")
..writeln("import 'package:flutter/material.dart';")
..writeln()
..writeln('//============================================================//')
..writeln(
'//============================================================//')
..writeln('//THIS FILE IS AUTO GENERATED. DO NOT EDIT//')
..writeln('//============================================================//')
..writeln('class LocalizationDelegate extends LocalizationsDelegate<Localization> {')
..writeln(" static const defaultLocale = Locale('${params.defaultLanguage}');")
..writeln(
'//============================================================//')
..writeln(
'class LocalizationDelegate extends LocalizationsDelegate<Localization> {')
..writeln(
" static const defaultLocale = Locale('${params.defaultLanguage}');")
..writeln(' static const supportedLanguages = [');
params.languages.forEach((language) => sb.writeln(" '$language',"));
sb..writeln(' ];')..writeln()..writeln(' static const supportedLocales = [');
params.languages.forEach((language) => sb.writeln(" Locale('$language'),"));
sb
..writeln(' ];')
..writeln()
..writeln(' static const supportedLocales = [');
params.languages
.forEach((language) => sb.writeln(" Locale('$language'),"));
sb
..writeln(' ];')
..writeln()
Expand All @@ -116,7 +148,8 @@ void createLocalizationDelegateFile() {
..writeln(' }')
..writeln()
..writeln(' @override')
..writeln(' bool isSupported(Locale locale) => supportedLanguages.contains(locale.languageCode);')
..writeln(
' bool isSupported(Locale locale) => supportedLanguages.contains(locale.languageCode);')
..writeln()
..writeln(' @override')
..writeln(' Future<Localization> load(Locale locale) async {')
Expand All @@ -125,12 +158,14 @@ void createLocalizationDelegateFile() {
..writeln(' }')
..writeln()
..writeln(' @override')
..writeln(' bool shouldReload(LocalizationsDelegate<Localization> old) => true;')
..writeln(
' bool shouldReload(LocalizationsDelegate<Localization> old) => true;')
..writeln()
..writeln('}');

// Write to file
final localizationDelegateFile = File(join(Directory.current.path, outputDir, 'localization_delegate.dart'));
final localizationDelegateFile = File(
join(Directory.current.path, outputDir, 'localization_delegate.dart'));
if (!localizationDelegateFile.existsSync()) {
print('localization_delegate.dart does not exists');
print('Creating localization_delegate.dart ...');
Expand Down
8 changes: 6 additions & 2 deletions bin/src/case_util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,19 @@ class CaseUtil {

for (var i = 0; i < text.length; i++) {
final char = String.fromCharCode(text.codeUnitAt(i));
final nextChar = text.length == i + 1 ? null : String.fromCharCode(text.codeUnitAt(i + 1));
final nextChar = text.length == i + 1
? null
: String.fromCharCode(text.codeUnitAt(i + 1));

if (_symbolRegex.hasMatch(char)) {
continue;
}

sb.write(char);

final isEndOfWord = nextChar == null || (_upperAlphaRegex.hasMatch(nextChar) && !isAllCaps) || _symbolRegex.hasMatch(nextChar);
final isEndOfWord = nextChar == null ||
(_upperAlphaRegex.hasMatch(nextChar) && !isAllCaps) ||
_symbolRegex.hasMatch(nextChar);

if (isEndOfWord) {
words.add(sb.toString());
Expand Down
9 changes: 6 additions & 3 deletions bin/src/params.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ class Params {
projectName = doc['name'];

if (projectName == null || projectName.isEmpty) {
throw Exception('Could not parse the pubspec.yaml, project name not found');
throw Exception(
'Could not parse the pubspec.yaml, project name not found');
}

final config = doc[localeGenYaml];
Expand All @@ -24,14 +25,16 @@ class Params {

final YamlList yamlList = config['languages'];
if (yamlList == null || yamlList.isEmpty) {
throw Exception("At least 1 language should be added to the 'languages' section in the pubspec.yaml\n"
throw Exception(
"At least 1 language should be added to the 'languages' section in the pubspec.yaml\n"
'$localeGenYaml\n'
" languages: ['en']");
}

languages = yamlList.map((item) => item.toString()).toList();
if (languages == null || languages.isEmpty) {
throw Exception("At least 1 language should be added to the 'languages' section in the pubspec.yaml\n"
throw Exception(
"At least 1 language should be added to the 'languages' section in the pubspec.yaml\n"
'$localeGenYaml\n'
" languages: ['en']");
}
Expand Down
79 changes: 79 additions & 0 deletions bin/src/translation_file_writer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import 'case_util.dart';

class FileWriter {
static final formatRegex = RegExp(r'\%(\d*)\$([a-z])');
static const REGEX_INDEX_GROUP_INDEX = 1;
static const REGEX_TYPE_GROUP_INDEX = 2;

static void buildTranslationFunction(
StringBuffer sb, String key, String value) {
if (value == null || value.isEmpty) {
_buildDefaultFunction(sb, key, value);
return;
}
final allMatched = formatRegex.allMatches(value);
if (allMatched == null || allMatched.isEmpty) {
_buildDefaultFunction(sb, key, value);
return;
}
try {
final tmpSb = StringBuffer(' String ${CaseUtil.getCamelcase(key)}(');

final validMatcher = List<RegExpMatch>();
allMatched.forEach((match) {
final sameTypeMatch = validMatcher.where((validMatch) =>
validMatch.group(REGEX_INDEX_GROUP_INDEX) ==
match.group(REGEX_INDEX_GROUP_INDEX));
if (sameTypeMatch.isNotEmpty &&
sameTypeMatch.first.group(REGEX_TYPE_GROUP_INDEX) !=
match.group(REGEX_TYPE_GROUP_INDEX)) {
throw Exception(
'$key contains a value with more than 1 argument with the same index but different type');
}
if (validMatcher
.where((validMatch) => validMatch.group(0) == match.group(0))
.isEmpty) {
validMatcher.add(match);
}
});

validMatcher.asMap().forEach((index, match) {
final argument = _getArgument(key, match);
tmpSb.write(argument);
if (index != validMatcher.length - 1) {
tmpSb.write(', ');
}
});
tmpSb.write(") => _t('$key', args: [");
validMatcher.asMap().forEach((index, match) {
if (index != 0) {
tmpSb.write(', ');
}
tmpSb.write('arg${match.group(REGEX_INDEX_GROUP_INDEX)}');
});
tmpSb..writeln(']);')..writeln();
sb.write(tmpSb.toString());
} on Exception catch (e) {
print(e);
_buildDefaultFunction(sb, key, value);
}
}

static String _getArgument(String key, RegExpMatch match) {
final index = match.group(REGEX_INDEX_GROUP_INDEX);
final type = match.group(REGEX_TYPE_GROUP_INDEX);
if (type == 's') {
return 'String arg$index';
} else if (type == 'd') {
return 'num arg$index';
}
throw Exception(
'Unsupported argument type for $key. Supported types are -> s,d. Create a github ticket for support -> https://github.com/vanlooverenkoen/locale_gen/issues');
}

static void _buildDefaultFunction(StringBuffer sb, String key, String value) {
sb
..writeln(" String get ${CaseUtil.getCamelcase(key)} => _t('$key');")
..writeln();
}
}
5 changes: 4 additions & 1 deletion example/assets/locale/en.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"app_title": "Locale Gen EN",
"test": "Testing in english"
"test": "Testing in english",
"test_arg1": "Testing argument %1$s",
"test_arg2": "Testing argument %1$d",
"test_arg3": "Testing argument %1$s %2$d"
}
5 changes: 4 additions & 1 deletion example/assets/locale/nl.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"app_title": "Locale Gen NL",
"test": "Test in het Nederlands"
"test": "Test in het Nederlands",
"test_arg1": "Test argument %1$s",
"test_arg2": "Test argument %1$d",
"test_arg3": "Test argument %1$s %2$d"
}
4 changes: 3 additions & 1 deletion example/lib/screen/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ class HomeScreen extends StatelessWidget {
onPressed: Provider.of<LocaleViewModel>(context).onSwitchToDutch,
),
Container(height: 32),
Text(Localization.of(context).test)
Text(Localization.of(context).test),
Text(Localization.of(context).testArg1('string')),
Text(Localization.of(context).testArg2(1.0))
],
),
),
Expand Down
Loading

0 comments on commit f9d5bd4

Please sign in to comment.