Skip to content

Commit

Permalink
Merge pull request #77 from vanlooverenkoen/feature/NonPositionalFormat
Browse files Browse the repository at this point in the history
Support non-positional format.
  • Loading branch information
vanlooverenkoen committed Sep 21, 2022
2 parents 9130644 + 6c80bf0 commit 3470af0
Show file tree
Hide file tree
Showing 11 changed files with 331 additions and 53 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,5 @@ app.*.symbols
!/dev/ci/**/Gemfile.lock

#FVM
**/.fvm/flutter_sdk
**/.fvm/flutter_sdk
/test/coverage_helper_test.dart
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# Changelog
## [6.0.0] - 2022-09-20
### Added
- Added support for non-positional arguments. You **cannot** use both positional and non-positional arguments in the same string.

## [5.0.0] - 2022-08-23
### Fixed
- Sorting of the default language in the supported languages
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ nl '%1$s, ik woon in %2$s. Wist je dat niet?' => KOEN, ik woon in ANTWERPEN. Wis
fr 'I live in %2$s. You didn't knew that %1$s?" => I live in ANTWERP. You didn't knew that KOEN?
```

*Note:* As of 6.0.0 non-positional arguments are also supported. You **cannot** use both positional and non-positional arguments in the same string.
Example:
```
'%s, ik woon in %s. Wist je dat niet?' => KOEN, ik woon in ANTWERPEN. Wist je dat niet?
```

### Working on mac?

add this to you .bash_profile
Expand Down
29 changes: 29 additions & 0 deletions lib/src/locale_gen_sb_writer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,22 @@ class LocaleGenSbWriter {
..writeln(' }')
..writeln(' }')
..writeln()
..writeln(' String _nonPositionalT(String key, {List<dynamic>? args}) {')
..writeln(' try {')
..writeln(
' final value = (_localisedOverrideValues[key] ?? _localisedValues[key]) as String?;')
..writeln(' if (value == null) return key;')
..writeln(' if (args == null || args.isEmpty) return value;')
..writeln(' var newValue = value;')
..writeln(' // ignore: avoid_annotating_with_dynamic')
..writeln(
' args.asMap().forEach((index, dynamic arg) => newValue = _replaceFirstWith(newValue, arg));')
..writeln(' return newValue;')
..writeln(' } catch (e) {')
..writeln(" return '⚠\$key⚠';")
..writeln(' }')
..writeln(' }')
..writeln()
..writeln(
' String _replaceWith(String value, Object? arg, int argIndex) {')
..writeln(' if (arg == null) return value;')
Expand All @@ -117,6 +133,16 @@ class LocaleGenSbWriter {
..writeln(' }')
..writeln(' return value;')
..writeln(' }')
..writeln()
..writeln(' String _replaceFirstWith(String value, Object? arg) {')
..writeln(' if (arg == null) return value;')
..writeln(' if (arg is String) {')
..writeln(" return value.replaceFirst('%s', arg);")
..writeln(' } else if (arg is num) {')
..writeln(" return value.replaceFirst('%d', '\$arg');")
..writeln(' }')
..writeln(' return value;')
..writeln(' }')
..writeln();
defaultTranslations.forEach((key, value) {
TranslationWriter.buildDocumentation(
Expand All @@ -127,6 +153,9 @@ class LocaleGenSbWriter {
..writeln(
' String getTranslation(String key, {List<dynamic>? args}) => _t(key, args: args ?? <dynamic>[]);')
..writeln()
..writeln(
' String getTranslationNonPositional(String key, {List<dynamic>? args}) => _nonPositionalT(key, args: args ?? <dynamic>[]);')
..writeln()
..writeln('}');
return sb.toString();
}
Expand Down
117 changes: 82 additions & 35 deletions lib/src/translation_writer.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'case_util.dart';

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

const TranslationWriter._();

Expand All @@ -13,46 +15,96 @@ class TranslationWriter {
_buildDefaultFunction(sb, key);
return;
}
final allMatched = formatRegex.allMatches(value);
if (allMatched.isEmpty) {
final allPositionalMatched = positionalFormatRegex.allMatches(value);
final allNormalMatched = normalFormatRegex.allMatches(value);
if (allPositionalMatched.isEmpty && allNormalMatched.isEmpty) {
_buildDefaultFunction(sb, key);
return;
}
if (allPositionalMatched.isNotEmpty && allNormalMatched.isNotEmpty) {
print(Exception(
'The translation for key "$key" contains both positional and normal format parameters'));
_buildDefaultFunction(sb, key);
return;
}

try {
if (allPositionalMatched.isNotEmpty) {
_buildPositionalParameterized(sb, key, value, allPositionalMatched);
} else {
_buildNormalParameterized(sb, key, value, allNormalMatched);
}
} on Exception catch (e) {
print(e);
_buildDefaultFunction(sb, key);
}
}

static void _buildPositionalParameterized(StringBuffer sb, String key,
String? value, Iterable<RegExpMatch> matches) {
// Validate
final validMatcher = <RegExpMatch>[];
matches.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);
}
});
final entries = validMatcher
.map((match) => MapEntry(
int.parse(match.group(REGEX_INDEX_GROUP_INDEX)!),
match.group(REGEX_TYPE_GROUP_INDEX)!))
.toList()
..sort((a, b) => a.key.compareTo(b.key));

final indexToReplacement = Map.fromEntries(entries);

_buildParameterizedFunction(sb, key, value, indexToReplacement, '_t');
}

static void _buildNormalParameterized(StringBuffer sb, String key,
String? value, Iterable<RegExpMatch> matches) {
var index = 1;
final entries = matches.map((match) =>
MapEntry(index++, match.group(NORMAL_REGEX_TYPE_GROUP_INDEX)!));
final indexToReplacement = Map.fromEntries(entries);

_buildParameterizedFunction(
sb, key, value, indexToReplacement, '_nonPositionalT');
}

static void _buildParameterizedFunction(StringBuffer sb, String key,
String? value, Map<int, String> indexToReplacement, String functionName) {
try {
final camelKey = CaseUtil.getCamelcase(key);
final tmpSb = StringBuffer(' String $camelKey(');

final validMatcher = <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);
var iterationIndex = 0;
indexToReplacement.forEach((index, match) {
final argument = _getArgument(key, match, index);
tmpSb.write(argument);
if (index != validMatcher.length - 1) {
if (iterationIndex++ != indexToReplacement.length - 1) {
tmpSb.write(', ');
}
});
tmpSb.write(') => _t(LocalizationKeys.$camelKey, args: <dynamic>[');
validMatcher.asMap().forEach((index, match) {
if (index != 0) {
tmpSb.write(
') => $functionName(LocalizationKeys.$camelKey, args: <dynamic>[');
iterationIndex = 0;
indexToReplacement.forEach((index, match) {
if (iterationIndex++ != 0) {
tmpSb.write(', ');
}
tmpSb.write('arg${match.group(REGEX_INDEX_GROUP_INDEX)}');
tmpSb.write('arg$index');
});
tmpSb
..writeln(']);')
Expand All @@ -64,9 +116,7 @@ class TranslationWriter {
}
}

static String _getArgument(String key, RegExpMatch match) {
final index = match.group(REGEX_INDEX_GROUP_INDEX);
final type = match.group(REGEX_TYPE_GROUP_INDEX);
static String _getArgument(String key, String type, int index) {
if (type == 's') {
return 'String arg$index';
} else if (type == 'd') {
Expand Down Expand Up @@ -109,17 +159,14 @@ class TranslationWriter {
static String? replaceArgumentDocumentation(String? value) {
if (value == null) return null;
var newValue = value;
final allMatches = formatRegex.allMatches(newValue);
final allMatches = positionalFormatRegex.allMatches(newValue);
for (final match in allMatches) {
final index = match.group(REGEX_INDEX_GROUP_INDEX);
final type = match.group(REGEX_TYPE_GROUP_INDEX);
if (type == 's') {
newValue = newValue.replaceAll('%$index\$$type', '[arg$index string]');
} else if (type == 'd') {
newValue = newValue.replaceAll('%$index\$$type', '[arg$index number]');
} else {
throw Exception(
'Unsupported argument type for $type. Supported types are -> s,d. Create a github ticket for support -> https://github.com/vanlooverenkoen/locale_gen/issues');
}
}
return newValue;
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: locale_gen
description: Dart tool that will convert your default locale json to dart code.
version: 5.0.0
version: 6.0.0
homepage: https://github.com/vanlooverenkoen/locale_gen

environment:
Expand Down
4 changes: 3 additions & 1 deletion test/assets/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
"test_arg2": "Testing argument %1$d",
"test_arg3": "Testing argument %1$s %2$d",
"test_arg4": "Testing argument %1$s %2$d %1$s",
"test_new_line": "Testing\nargument\n\n%1$s %2$d %1$s"
"test_new_line": "Testing\nargument\n\n%1$s %2$d %1$s",
"test_arg_non_positional": "Testing argument %s",
"test_arg_non_positional2": "Testing argument %s %d"
}
4 changes: 3 additions & 1 deletion test/assets/locale/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
"test_arg3": "Test argument %1$s %2$d",
"test_arg4": "Test argument %1$s %2$d %1$s",
"welcome_message": "Hallo daar",
"test_new_line": "Test\nargument\n\n%1$s %2$d %1$s"
"test_new_line": "Test\nargument\n\n%1$s %2$d %1$s",
"test_arg_non_positional": "Test argument %s",
"test_arg_non_positional2": "Test argument %s %d"
}

0 comments on commit 3470af0

Please sign in to comment.