Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
c319f5f8
Unverified
Commit
c319f5f8
authored
Dec 17, 2019
by
Shi-Hao Hong
Committed by
GitHub
Dec 17, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
gen_l10n localizations date formatting (simple messages) (#47006)
* Implement date parsing for the gen_l10n tool
parent
d4b49ce9
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
369 additions
and
4 deletions
+369
-4
gen_l10n.dart
dev/tools/localization/gen_l10n.dart
+138
-4
gen_l10n_test.dart
dev/tools/test/localization/gen_l10n_test.dart
+231
-0
No files found.
dev/tools/localization/gen_l10n.dart
View file @
c319f5f8
...
...
@@ -136,7 +136,7 @@ const String getterMethodTemplate = '''
''';
const String simpleMethodTemplate = '''
String
@methodName
(
@methodParameters
)
{
String
@methodName
(
@methodParameters
)
{
@dateFormatting
return
Intl
.
message
(
@message
,
locale:
_localeName
,
...
...
@@ -153,6 +153,96 @@ const String pluralMethodTemplate = '''
}
''';
// The set of date formats that can be automatically localized.
//
// The localizations generation tool makes use of the intl library'
s
// DateFormat class to properly format dates based on the locale, the
// desired format, as well as the passed in [DateTime]. For example, using
// DateFormat.yMMMMd("en_US").format(DateTime.utc(1996, 7, 10)) results
// in the string "July 10, 1996".
//
// Since the tool generates code that uses DateFormat's constructor, it is
// necessary to verify that the constructor exists, or the
// tool will generate code that may cause a compile-time error.
//
// See also:
//
// * <https://pub.dev/packages/intl>
// * <https://pub.dev/documentation/intl/latest/intl/DateFormat-class.html>
// * <https://api.dartlang.org/stable/2.7.0/dart-core/DateTime-class.html>
const
Set
<
String
>
allowableDateFormats
=
<
String
>{
'd'
,
'E'
,
'EEEE'
,
'LLL'
,
'LLLL'
,
'M'
,
'Md'
,
'MEd'
,
'MMM'
,
'MMMd'
,
'MMMEd'
,
'MMMM'
,
'MMMMd'
,
'MMMMEEEEd'
,
'QQQ'
,
'QQQQ'
,
'y'
,
'yM'
,
'yMd'
,
'yMEd'
,
'yMMM'
,
'yMMMd'
,
'yMMMEd'
,
'yMMMM'
,
'yMMMMd'
,
'yMMMMEEEEd'
,
'yQQQ'
,
'yQQQQ'
,
'H'
,
'Hm'
,
'Hms'
,
'j'
,
'jm'
,
'jms'
,
'jmv'
,
'jmz'
,
'jv'
,
'jz'
,
'm'
,
'ms'
,
's'
,
};
bool
_isDateParameter
(
dynamic
placeholderValue
)
{
return
placeholderValue
is
Map
<
String
,
dynamic
>
&&
placeholderValue
[
'type'
]
==
'DateTime'
;
}
bool
_dateParameterIsValid
(
Map
<
String
,
dynamic
>
placeholderValue
,
String
placeholder
)
{
if
(
allowableDateFormats
.
contains
(
placeholderValue
[
'format'
]))
return
true
;
throw
L10nException
(
'Date format
${placeholderValue['format']}
for the
$placeholder
\n
'
'placeholder does not have a corresponding DateFormat
\n
'
'constructor. Check the intl library
\'
s DateFormat class
\n
'
'constructors for allowed date formats.'
);
}
bool
_containsFormatKey
(
Map
<
String
,
dynamic
>
placeholderValue
,
String
placeholder
)
{
if
(
placeholderValue
.
containsKey
(
'format'
))
return
true
;
throw
L10nException
(
'The placeholder,
$placeholder
, has its "type" resource attribute set to '
'the "DateTime" type. To properly resolve for the right DateTime format, '
'the "format" attribute needs to be set to determine which DateFormat to '
'use.
\n
'
'Check the intl library
\'
s DateFormat class constructors for allowed '
'date formats.'
);
}
List
<
String
>
genMethodParameters
(
Map
<
String
,
dynamic
>
bundle
,
String
key
,
String
type
)
{
final
Map
<
String
,
dynamic
>
attributesMap
=
bundle
[
'@
$key
'
]
as
Map
<
String
,
dynamic
>;
if
(
attributesMap
!=
null
&&
attributesMap
.
containsKey
(
'placeholders'
))
{
...
...
@@ -162,6 +252,30 @@ List<String> genMethodParameters(Map<String, dynamic> bundle, String key, String
return
<
String
>[];
}
String
generateDateFormattingLogic
(
Map
<
String
,
dynamic
>
bundle
,
String
key
)
{
String
result
=
''
;
final
Map
<
String
,
dynamic
>
attributesMap
=
bundle
[
'@
$key
'
]
as
Map
<
String
,
dynamic
>;
if
(
attributesMap
!=
null
&&
attributesMap
.
containsKey
(
'placeholders'
))
{
final
Map
<
String
,
dynamic
>
placeholders
=
attributesMap
[
'placeholders'
]
as
Map
<
String
,
dynamic
>;
for
(
String
placeholder
in
placeholders
.
keys
)
{
final
dynamic
value
=
placeholders
[
placeholder
];
if
(
_isDateParameter
(
value
)
&&
_containsFormatKey
(
value
,
placeholder
)
&&
_dateParameterIsValid
(
value
,
placeholder
)
)
{
result
+=
'''
final DateFormat
${placeholder}
DateFormat = DateFormat.
${value['format']}
(_localeName);
final String
${placeholder}
String =
${placeholder}
DateFormat.format(
$placeholder
);
'''
;
}
}
}
return
result
;
}
List
<
String
>
genIntlMethodArgs
(
Map
<
String
,
dynamic
>
bundle
,
String
key
)
{
final
List
<
String
>
attributes
=
<
String
>[
'name:
\'
$key
\'
'
];
final
Map
<
String
,
dynamic
>
attributesMap
=
bundle
[
'@
$key
'
]
as
Map
<
String
,
dynamic
>;
...
...
@@ -173,7 +287,20 @@ List<String> genIntlMethodArgs(Map<String, dynamic> bundle, String key) {
if
(
attributesMap
.
containsKey
(
'placeholders'
))
{
final
Map
<
String
,
dynamic
>
placeholders
=
attributesMap
[
'placeholders'
]
as
Map
<
String
,
dynamic
>;
if
(
placeholders
.
isNotEmpty
)
{
final
String
args
=
placeholders
.
keys
.
join
(
', '
);
final
List
<
String
>
argumentList
=
<
String
>[];
for
(
String
placeholder
in
placeholders
.
keys
)
{
final
dynamic
value
=
placeholders
[
placeholder
];
if
(
_isDateParameter
(
value
)
&&
_containsFormatKey
(
value
,
placeholder
)
&&
_dateParameterIsValid
(
value
,
placeholder
)
)
{
argumentList
.
add
(
'
${placeholder}
String'
);
}
else
{
argumentList
.
add
(
placeholder
);
}
}
final
String
args
=
argumentList
.
join
(
', '
);
attributes
.
add
(
'args: <Object>[
$args
]'
);
}
}
...
...
@@ -186,8 +313,14 @@ String genSimpleMethod(Map<String, dynamic> bundle, String key) {
String
message
=
bundle
[
key
]
as
String
;
final
Map
<
String
,
dynamic
>
attributesMap
=
bundle
[
'@
$key
'
]
as
Map
<
String
,
dynamic
>;
final
Map
<
String
,
dynamic
>
placeholders
=
attributesMap
[
'placeholders'
]
as
Map
<
String
,
dynamic
>;
for
(
String
placeholder
in
placeholders
.
keys
)
message
=
message
.
replaceAll
(
'{
$placeholder
}'
,
'
\$
$placeholder
'
);
for
(
String
placeholder
in
placeholders
.
keys
)
{
final
dynamic
value
=
placeholders
[
placeholder
];
if
(
_isDateParameter
(
value
))
{
message
=
message
.
replaceAll
(
'{
$placeholder
}'
,
'
\$
${placeholder}
String'
);
}
else
{
message
=
message
.
replaceAll
(
'{
$placeholder
}'
,
'
\$
$placeholder
'
);
}
}
return
generateString
(
message
);
}
...
...
@@ -202,6 +335,7 @@ String genSimpleMethod(Map<String, dynamic> bundle, String key) {
return
simpleMethodTemplate
.
replaceAll
(
'@methodName'
,
key
)
.
replaceAll
(
'@methodParameters'
,
genMethodParameters
(
bundle
,
key
,
'Object'
).
join
(
', '
))
.
replaceAll
(
'@dateFormatting'
,
generateDateFormattingLogic
(
bundle
,
key
))
.
replaceAll
(
'@message'
,
'
${genSimpleMethodMessage(bundle, key)}
'
)
.
replaceAll
(
'@intlMethodArgs'
,
genIntlMethodArgs
(
bundle
,
key
).
join
(
',
\n
'
));
}
...
...
dev/tools/test/localization/gen_l10n_test.dart
View file @
c319f5f8
...
...
@@ -554,6 +554,237 @@ void main() {
'''
);
});
test
(
'correctly generates simple message with dates'
,
()
{
const
String
singleDateMessageArbFileString
=
'''{
"springBegins": "Spring begins on {springStartDate}",
"@springBegins": {
"description": "The first day of spring",
"placeholders": {
"springStartDate": {
"type": "DateTime",
"format": "yMMMMEEEEd"
}
}
}
}'''
;
final
Directory
l10nDirectory
=
fs
.
currentDirectory
.
childDirectory
(
'lib'
).
childDirectory
(
'l10n'
)
..
createSync
(
recursive:
true
);
l10nDirectory
.
childFile
(
defaultTemplateArbFileName
)
.
writeAsStringSync
(
singleDateMessageArbFileString
);
final
LocalizationsGenerator
generator
=
LocalizationsGenerator
(
fs
);
try
{
generator
.
initialize
(
l10nDirectoryPath:
defaultArbPathString
,
templateArbFileName:
defaultTemplateArbFileName
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
);
generator
.
parseArbFiles
();
generator
.
generateClassMethods
();
}
on
Exception
catch
(
e
)
{
fail
(
'Parsing template arb file should succeed:
\n
$e
'
);
}
expect
(
generator
.
classMethods
,
isNotEmpty
);
expect
(
generator
.
classMethods
.
first
,
''' String springBegins(Object springStartDate) {
final DateFormat springStartDateDateFormat = DateFormat.yMMMMEEEEd(_localeName);
final String springStartDateString = springStartDateDateFormat.format(springStartDate);
return Intl.message(
r'
Spring
begins
on
\
$springStartDateString
',
locale: _localeName,
name: '
springBegins
',
desc: r'
The
first
day
of
spring
',
args: <Object>[springStartDateString]
);
}
'''
);
});
test
(
'throws an exception when improperly formatted date is passed in'
,
()
{
const
String
singleDateMessageArbFileString
=
'''{
"springBegins": "Spring begins on {springStartDate}",
"@springBegins": {
"description": "The first day of spring",
"placeholders": {
"springStartDate": {
"type": "DateTime",
"format": "asdf"
}
}
}
}'''
;
final
Directory
l10nDirectory
=
fs
.
currentDirectory
.
childDirectory
(
'lib'
).
childDirectory
(
'l10n'
)
..
createSync
(
recursive:
true
);
l10nDirectory
.
childFile
(
defaultTemplateArbFileName
)
.
writeAsStringSync
(
singleDateMessageArbFileString
);
final
LocalizationsGenerator
generator
=
LocalizationsGenerator
(
fs
);
try
{
generator
.
initialize
(
l10nDirectoryPath:
defaultArbPathString
,
templateArbFileName:
defaultTemplateArbFileName
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
);
generator
.
parseArbFiles
();
generator
.
generateClassMethods
();
}
on
L10nException
catch
(
e
)
{
expect
(
e
.
message
,
contains
(
'asdf'
));
expect
(
e
.
message
,
contains
(
'springStartDate'
));
expect
(
e
.
message
,
contains
(
'does not have a corresponding DateFormat'
));
return
;
}
fail
(
'Improper date formatting should throw an exception'
);
});
test
(
'throws an exception when no format attribute is passed in'
,
()
{
const
String
singleDateMessageArbFileString
=
'''{
"springBegins": "Spring begins on {springStartDate}",
"@springBegins": {
"description": "The first day of spring",
"placeholders": {
"springStartDate": {
"type": "DateTime"
}
}
}
}'''
;
final
Directory
l10nDirectory
=
fs
.
currentDirectory
.
childDirectory
(
'lib'
).
childDirectory
(
'l10n'
)
..
createSync
(
recursive:
true
);
l10nDirectory
.
childFile
(
defaultTemplateArbFileName
)
.
writeAsStringSync
(
singleDateMessageArbFileString
);
final
LocalizationsGenerator
generator
=
LocalizationsGenerator
(
fs
);
try
{
generator
.
initialize
(
l10nDirectoryPath:
defaultArbPathString
,
templateArbFileName:
defaultTemplateArbFileName
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
);
generator
.
parseArbFiles
();
generator
.
generateClassMethods
();
}
on
L10nException
catch
(
e
)
{
expect
(
e
.
message
,
contains
(
'the "format" attribute needs to be set'
));
return
;
}
fail
(
'Improper date formatting should throw an exception'
);
});
test
(
'correctly generates simple message with date along with other placeholders'
,
()
{
const
String
singleDateMessageArbFileString
=
'''{
"springGreetings": "Since it'
s
{
springStartDate
},
it
's finally spring! {helloWorld}!",
"@springGreetings": {
"description": "A realization that it'
s
finally
the
spring
season
,
followed
by
a
greeting
.
",
"
placeholders
": {
"
springStartDate
": {
"
type
": "
DateTime
",
"
format
": "
yMMMMEEEEd
"
},
"
helloWorld
": {}
}
}
}''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singleDateMessageArbFileString);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.initialize(
l10nDirectoryPath: defaultArbPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
);
generator.parseArbFiles();
generator.generateClassMethods();
} on Exception catch (e) {
fail('Parsing template arb file should succeed:
\n
$e
');
}
expect(generator.classMethods, isNotEmpty);
expect(
generator.classMethods.first,
''' String springGreetings(Object springStartDate, Object helloWorld) {
final DateFormat springStartDateDateFormat = DateFormat.yMMMMEEEEd(_localeName);
final String springStartDateString = springStartDateDateFormat.format(springStartDate);
return Intl.message(
r
\'
Since it
\'
"
\
'" r
\'
s
\
$springStartDateString
, it
\'
"
\'
" r
\'
s finally spring!
\
$helloWorld
!
\'
,
locale: _localeName,
name: '
springGreetings
',
desc: r
\'
A realization that it
\'
"
\'
" r
\'
s finally the spring season, followed by a greeting.
\'
,
args: <Object>[springStartDateString, helloWorld]
);
}
'''
);
});
test
(
'correctly generates simple message with multiple dates'
,
()
{
const
String
singleDateMessageArbFileString
=
'''{
"springRange": "Spring begins on {springStartDate} and ends on {springEndDate}",
"@springRange": {
"description": "The range of dates for spring in the year",
"placeholders": {
"springStartDate": {
"type": "DateTime",
"format": "yMMMMEEEEd"
},
"springEndDate": {
"type": "DateTime",
"format": "yMMMMEEEEd"
}
}
}
}'''
;
final
Directory
l10nDirectory
=
fs
.
currentDirectory
.
childDirectory
(
'lib'
).
childDirectory
(
'l10n'
)
..
createSync
(
recursive:
true
);
l10nDirectory
.
childFile
(
defaultTemplateArbFileName
)
.
writeAsStringSync
(
singleDateMessageArbFileString
);
final
LocalizationsGenerator
generator
=
LocalizationsGenerator
(
fs
);
try
{
generator
.
initialize
(
l10nDirectoryPath:
defaultArbPathString
,
templateArbFileName:
defaultTemplateArbFileName
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
);
generator
.
parseArbFiles
();
generator
.
generateClassMethods
();
}
on
Exception
catch
(
e
)
{
fail
(
'Parsing template arb file should succeed:
\n
$e
'
);
}
expect
(
generator
.
classMethods
,
isNotEmpty
);
expect
(
generator
.
classMethods
.
first
,
''' String springRange(Object springStartDate, Object springEndDate) {
final DateFormat springStartDateDateFormat = DateFormat.yMMMMEEEEd(_localeName);
final String springStartDateString = springStartDateDateFormat.format(springStartDate);
final DateFormat springEndDateDateFormat = DateFormat.yMMMMEEEEd(_localeName);
final String springEndDateString = springEndDateDateFormat.format(springEndDate);
return Intl.message(
r
\'
Spring begins on
\
$springStartDateString
and ends on
\
$springEndDateString
\'
,
locale: _localeName,
name: '
springRange
',
desc: r
\'
The range of dates for spring in the year
\'
,
args: <Object>[springStartDateString, springEndDateString]
);
}
'''
);
});
test
(
'correctly generates a plural message:'
,
()
{
const
String
singlePluralMessageArbFileString
=
'''{
"helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}",
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment