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
13e1336e
Unverified
Commit
13e1336e
authored
Feb 25, 2020
by
Hans Muller
Committed by
GitHub
Feb 25, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Generate message lookup in gen_l10n (#50733)
parent
9b3754d5
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
688 additions
and
770 deletions
+688
-770
gen_l10n.dart
dev/tools/localization/bin/gen_l10n.dart
+5
-26
gen_l10n.dart
dev/tools/localization/gen_l10n.dart
+244
-412
gen_l10n_templates.dart
dev/tools/localization/gen_l10n_templates.dart
+106
-82
gen_l10n_types.dart
dev/tools/localization/gen_l10n_types.dart
+275
-0
gen_l10n_test.dart
dev/tools/test/localization/gen_l10n_test.dart
+58
-250
No files found.
dev/tools/localization/bin/gen_l10n.dart
View file @
13e1336e
...
@@ -63,9 +63,8 @@ Future<void> main(List<String> arguments) async {
...
@@ -63,9 +63,8 @@ Future<void> main(List<String> arguments) async {
exit
(
0
);
exit
(
0
);
}
}
final
String
flutterRoot
=
Platform
.
environment
[
'FLUTTER_ROOT'
];
await
precacheLanguageAndRegionTags
();
final
String
flutterBin
=
Platform
.
isWindows
?
'flutter.bat'
:
'flutter'
;
final
String
flutterPath
=
flutterRoot
==
null
?
flutterBin
:
path
.
join
(
flutterRoot
,
'bin'
,
flutterBin
);
final
String
arbPathString
=
results
[
'arb-dir'
]
as
String
;
final
String
arbPathString
=
results
[
'arb-dir'
]
as
String
;
final
String
outputFileString
=
results
[
'output-localization-file'
]
as
String
;
final
String
outputFileString
=
results
[
'output-localization-file'
]
as
String
;
final
String
templateArbFileName
=
results
[
'template-arb-file'
]
as
String
;
final
String
templateArbFileName
=
results
[
'template-arb-file'
]
as
String
;
...
@@ -74,6 +73,7 @@ Future<void> main(List<String> arguments) async {
...
@@ -74,6 +73,7 @@ Future<void> main(List<String> arguments) async {
const
local
.
LocalFileSystem
fs
=
local
.
LocalFileSystem
();
const
local
.
LocalFileSystem
fs
=
local
.
LocalFileSystem
();
final
LocalizationsGenerator
localizationsGenerator
=
LocalizationsGenerator
(
fs
);
final
LocalizationsGenerator
localizationsGenerator
=
LocalizationsGenerator
(
fs
);
try
{
try
{
localizationsGenerator
localizationsGenerator
..
initialize
(
..
initialize
(
...
@@ -83,9 +83,8 @@ Future<void> main(List<String> arguments) async {
...
@@ -83,9 +83,8 @@ Future<void> main(List<String> arguments) async {
classNameString:
classNameString
,
classNameString:
classNameString
,
preferredSupportedLocaleString:
preferredSupportedLocaleString
,
preferredSupportedLocaleString:
preferredSupportedLocaleString
,
)
)
..
parseArbFiles
()
..
loadResources
()
..
generateClassMethods
()
..
writeOutputFile
();
..
generateOutputFile
();
}
on
FileSystemException
catch
(
e
)
{
}
on
FileSystemException
catch
(
e
)
{
exitWithError
(
e
.
message
);
exitWithError
(
e
.
message
);
}
on
FormatException
catch
(
e
)
{
}
on
FormatException
catch
(
e
)
{
...
@@ -93,24 +92,4 @@ Future<void> main(List<String> arguments) async {
...
@@ -93,24 +92,4 @@ Future<void> main(List<String> arguments) async {
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
exitWithError
(
e
.
message
);
exitWithError
(
e
.
message
);
}
}
final
ProcessResult
pubGetResult
=
await
Process
.
run
(
flutterPath
,
<
String
>[
'pub'
,
'get'
]);
if
(
pubGetResult
.
exitCode
!=
0
)
{
stderr
.
write
(
pubGetResult
.
stderr
);
exit
(
1
);
}
final
ProcessResult
generateFromArbResult
=
await
Process
.
run
(
flutterPath
,
<
String
>[
'pub'
,
'run'
,
'intl_translation:generate_from_arb'
,
'--output-dir=
${localizationsGenerator.l10nDirectory.path}
'
,
'--no-use-deferred-loading'
,
localizationsGenerator
.
outputFile
.
path
,
...
localizationsGenerator
.
arbPathStrings
,
]);
if
(
generateFromArbResult
.
exitCode
!=
0
)
{
stderr
.
write
(
generateFromArbResult
.
stderr
);
exit
(
1
);
}
}
}
dev/tools/localization/gen_l10n.dart
View file @
13e1336e
...
@@ -13,255 +13,78 @@ import 'gen_l10n_templates.dart';
...
@@ -13,255 +13,78 @@ import 'gen_l10n_templates.dart';
import
'gen_l10n_types.dart'
;
import
'gen_l10n_types.dart'
;
import
'localizations_utils.dart'
;
import
'localizations_utils.dart'
;
// The set of date formats that can be automatically localized.
List
<
String
>
generateMethodParameters
(
Message
message
)
{
//
assert
(
message
.
placeholders
.
isNotEmpty
);
// The localizations generation tool makes use of the intl library's
final
Placeholder
countPlaceholder
=
message
.
isPlural
?
message
.
getCountPlaceholder
()
:
null
;
// 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'
,
};
// The set of number formats that can be automatically localized.
//
// The localizations generation tool makes use of the intl library's
// NumberFormat class to properly format numbers based on the locale, the
// desired format, as well as the passed in number. For example, using
// DateFormat.compactLong("en_US").format(1200000) results
// in the string "1.2 million".
//
// Since the tool generates code that uses NumberFormat'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/NumberFormat-class.html>
const
Set
<
String
>
allowableNumberFormats
=
<
String
>{
'compact'
,
'compactCurrency'
,
'compactSimpleCurrency'
,
'compactLong'
,
'currency'
,
'decimalPattern'
,
'decimalPercentPattern'
,
'percentPattern'
,
'scientificPattern'
,
'simpleCurrency'
,
};
// The names of the NumberFormat factory constructors which have named
// parameters rather than positional parameters.
//
// This helps the tool correctly generate number formmatting code correctly.
//
// Example of code that uses named parameters:
// final NumberFormat format = NumberFormat.compact(
// locale: _localeName,
// );
//
// Example of code that uses positional parameters:
// final NumberFormat format = NumberFormat.scientificPattern(_localeName);
const
Set
<
String
>
numberFormatsWithNamedParameters
=
<
String
>{
'compact'
,
'compactCurrency'
,
'compactSimpleCurrency'
,
'compactLong'
,
'currency'
,
'decimalPercentPattern'
,
'simpleCurrency'
,
};
List
<
String
>
generateIntlMethodArgs
(
Message
message
)
{
final
List
<
String
>
methodArgs
=
<
String
>[
"name: '
${message.resourceId}
'"
];
if
(
message
.
description
!=
null
)
methodArgs
.
add
(
'desc:
${generateString(message.description)}
'
);
if
(
message
.
placeholders
.
isNotEmpty
)
{
final
String
args
=
message
.
placeholders
.
map
<
String
>((
Placeholder
placeholder
)
{
return
placeholder
.
name
;
}).
join
(
', '
);
methodArgs
.
add
(
'args: <Object>[
$args
]'
);
}
return
methodArgs
;
}
List
<
String
>
generateInnerMethodArgs
(
Message
message
)
{
return
message
.
placeholders
.
map
((
Placeholder
placeholder
)
{
return
message
.
placeholders
.
map
((
Placeholder
placeholder
)
{
final
String
arg
=
placeholder
.
nam
e
;
final
String
type
=
placeholder
==
countPlaceholder
?
'int'
:
placeholder
.
typ
e
;
return
placeholder
.
requiresFormatting
?
'
${arg}
String'
:
arg
;
return
'
$type
${placeholder.name}
'
;
}).
toList
();
}).
toList
();
}
}
String
generateDateFormattingLogic
(
Message
message
)
{
String
generateDateFormattingLogic
(
Message
message
)
{
if
(
message
.
placeholders
.
isEmpty
)
if
(
message
.
placeholders
.
isEmpty
||
!
message
.
placeholdersRequireFormatting
)
return
''
;
return
'@(none)'
;
final
StringBuffer
result
=
StringBuffer
();
for
(
final
Placeholder
placeholder
in
message
.
placeholders
)
{
if
(!
placeholder
.
isDate
)
continue
;
if
(
placeholder
.
format
==
null
)
{
throw
L10nException
(
'The placeholder,
${placeholder.name}
, has its "type" resource attribute set to '
'the "
${placeholder.type}
" type. To properly resolve for the right '
'
${placeholder.type}
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.'
);
}
if
(!
allowableDateFormats
.
contains
(
placeholder
.
format
))
{
throw
L10nException
(
'Date format "
${placeholder.format}
" for placeholder '
'
${placeholder.name}
does not have a corresponding DateFormat '
"constructor
\n
. Check the intl library's DateFormat class "
'constructors for allowed date formats.'
);
}
result
.
write
(
'''
final DateFormat
${placeholder.name}
DateFormat = DateFormat.
${placeholder.format}
(_localeName);
final String
${placeholder.name}
String =
${placeholder.name}
DateFormat.format(
${placeholder.name}
);
'''
);
}
return
result
.
toString
();
}
String
generateNumberFormattingLogic
(
Message
message
)
{
if
(
message
.
placeholders
.
isEmpty
)
return
''
;
final
StringBuffer
result
=
StringBuffer
();
for
(
final
Placeholder
placeholder
in
message
.
placeholders
)
{
if
(!
placeholder
.
isNumber
)
continue
;
if
(!
allowableNumberFormats
.
contains
(
placeholder
.
format
))
{
throw
L10nException
(
'Number format
${placeholder.format}
for the
${placeholder.name}
'
'placeholder does not have a corresponding NumberFormat constructor.
\n
'
"Check the intl library's NumberFormat class constructors for allowed "
'number formats.'
);
}
if
(
numberFormatsWithNamedParameters
.
contains
(
placeholder
.
format
))
{
final
StringBuffer
optionalParametersString
=
StringBuffer
();
for
(
final
OptionalParameter
parameter
in
placeholder
.
optionalParameters
)
optionalParametersString
.
write
(
'
\n
${parameter.name}
:
${parameter.value}
,'
);
result
.
write
(
'''
final NumberFormat
${placeholder.name}
NumberFormat = NumberFormat.
${placeholder.format}
(
locale: _localeName,
${optionalParametersString.toString()}
);
final String
${placeholder.name}
String =
${placeholder.name}
NumberFormat.format(
${placeholder.name}
);
'''
);
}
else
{
final
Iterable
<
String
>
formatStatements
=
message
.
placeholders
result
.
write
(
'''
.
where
((
Placeholder
placeholder
)
=>
placeholder
.
isDate
)
.
map
((
Placeholder
placeholder
)
{
if
(
placeholder
.
format
==
null
)
{
throw
L10nException
(
'The placeholder,
${placeholder.name}
, has its "type" resource attribute set to '
'the "
${placeholder.type}
" type. To properly resolve for the right '
'
${placeholder.type}
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.'
);
}
if
(!
placeholder
.
hasValidDateFormat
)
{
throw
L10nException
(
'Date format "
${placeholder.format}
" for placeholder '
'
${placeholder.name}
does not have a corresponding DateFormat '
'constructor
\n
. Check the intl library
\'
s DateFormat class '
'constructors for allowed date formats.'
);
}
return
dateFormatTemplate
.
replaceAll
(
'@(placeholder)'
,
placeholder
.
name
)
.
replaceAll
(
'@(format)'
,
placeholder
.
format
);
});
final NumberFormat
${placeholder.name}
NumberFormat = NumberFormat.
${placeholder.format}
(_localeName);
return
formatStatements
.
isEmpty
?
'@(none)'
:
formatStatements
.
join
(
''
);
final String
${placeholder.name}
String =
${placeholder.name}
NumberFormat.format(
${placeholder.name}
);
'''
);
}
}
return
result
.
toString
();
}
}
String
genSimpleMethod
(
Message
message
)
{
String
generateNumberFormattingLogic
(
Message
message
)
{
String
genSimpleMethodMessage
()
{
if
(
message
.
placeholders
.
isEmpty
||
!
message
.
placeholdersRequireFormatting
)
{
String
messageValue
=
message
.
value
;
return
'@(none)'
;
for
(
final
Placeholder
placeholder
in
message
.
placeholders
)
{
messageValue
=
messageValue
.
replaceAll
(
'{
${placeholder.name}
}'
,
'
\
${${placeholder.name}
}'
);
}
final
String
generatedMessage
=
generateString
(
messageValue
);
// "r'...'"
return
generatedMessage
.
startsWith
(
'r'
)
?
generatedMessage
.
substring
(
1
)
:
generatedMessage
;
}
List
<
String
>
genMethodParameters
([
String
type
])
{
return
message
.
placeholders
.
map
((
Placeholder
placeholder
)
{
return
'
${type ?? placeholder.type}
${placeholder.name}
'
;
}).
toList
();
}
if
(
message
.
placeholdersRequireFormatting
)
{
return
formatMethodTemplate
.
replaceAll
(
'@(methodName)'
,
message
.
resourceId
)
.
replaceAll
(
'@(methodParameters)'
,
genMethodParameters
().
join
(
', '
))
.
replaceAll
(
'@(dateFormatting)'
,
generateDateFormattingLogic
(
message
))
.
replaceAll
(
'@(numberFormatting)'
,
generateNumberFormattingLogic
(
message
))
.
replaceAll
(
'@(message)'
,
genSimpleMethodMessage
())
.
replaceAll
(
'@(innerMethodParameters)'
,
genMethodParameters
(
'Object'
).
join
(
', '
))
.
replaceAll
(
'@(innerMethodArgs)'
,
generateInnerMethodArgs
(
message
).
join
(
', '
))
.
replaceAll
(
'@(intlMethodArgs)'
,
generateIntlMethodArgs
(
message
).
join
(
',
\n
'
));
}
}
if
(
message
.
placeholders
.
isNotEmpty
)
{
final
Iterable
<
String
>
formatStatements
=
message
.
placeholders
return
simpleMethodTemplate
.
where
((
Placeholder
placeholder
)
=>
placeholder
.
isNumber
)
.
replaceAll
(
'@(methodName)'
,
message
.
resourceId
)
.
map
((
Placeholder
placeholder
)
{
.
replaceAll
(
'@(methodParameters)'
,
genMethodParameters
().
join
(
', '
))
if
(!
placeholder
.
hasValidNumberFormat
)
{
.
replaceAll
(
'@(message)'
,
genSimpleMethodMessage
())
throw
L10nException
(
.
replaceAll
(
'@(intlMethodArgs)'
,
generateIntlMethodArgs
(
message
).
join
(
',
\n
'
));
'Number format
${placeholder.format}
for the
${placeholder.name}
'
}
'placeholder does not have a corresponding NumberFormat constructor.
\n
'
'Check the intl library
\'
s NumberFormat class constructors for allowed '
'number formats.'
);
}
final
Iterable
<
String
>
parameters
=
placeholder
.
optionalParameters
.
map
<
String
>((
OptionalParameter
parameter
)
{
return
'
${parameter.name}
:
${parameter.value}
'
;
},
);
return
numberFormatTemplate
.
replaceAll
(
'@(placeholder)'
,
placeholder
.
name
)
.
replaceAll
(
'@(format)'
,
placeholder
.
format
)
.
replaceAll
(
'@(parameters)'
,
parameters
.
join
(
',
\n
'
));
});
return
getterMethodTemplate
return
formatStatements
.
isEmpty
?
'@(none)'
:
formatStatements
.
join
(
''
);
.
replaceAll
(
'@(methodName)'
,
message
.
resourceId
)
.
replaceAll
(
'@(message)'
,
genSimpleMethodMessage
())
.
replaceAll
(
'@(intlMethodArgs)'
,
generateIntlMethodArgs
(
message
).
join
(
',
\n
'
));
}
}
String
generatePluralMethod
(
Message
message
)
{
String
generatePluralMethod
(
Message
message
)
{
if
(
message
.
placeholders
.
isEmpty
)
{
if
(
message
.
placeholders
.
isEmpty
)
{
throw
L10nException
(
throw
L10nException
(
...
@@ -277,15 +100,6 @@ String generatePluralMethod(Message message) {
...
@@ -277,15 +100,6 @@ String generatePluralMethod(Message message) {
for
(
final
Placeholder
placeholder
in
message
.
placeholders
)
for
(
final
Placeholder
placeholder
in
message
.
placeholders
)
easyMessage
=
easyMessage
.
replaceAll
(
'{
${placeholder.name}
}'
,
'#
${placeholder.name}
#'
);
easyMessage
=
easyMessage
.
replaceAll
(
'{
${placeholder.name}
}'
,
'#
${placeholder.name}
#'
);
const
Map
<
String
,
String
>
pluralIds
=
<
String
,
String
>{
'=0'
:
'zero'
,
'=1'
:
'one'
,
'=2'
:
'two'
,
'few'
:
'few'
,
'many'
:
'many'
,
'other'
:
'other'
};
final
Placeholder
countPlaceholder
=
message
.
getCountPlaceholder
();
final
Placeholder
countPlaceholder
=
message
.
getCountPlaceholder
();
if
(
countPlaceholder
==
null
)
{
if
(
countPlaceholder
==
null
)
{
throw
L10nException
(
throw
L10nException
(
...
@@ -295,67 +109,154 @@ String generatePluralMethod(Message message) {
...
@@ -295,67 +109,154 @@ String generatePluralMethod(Message message) {
);
);
}
}
final
List
<
String
>
intlMethodArgs
=
<
String
>[
const
Map
<
String
,
String
>
pluralIds
=
<
String
,
String
>{
countPlaceholder
.
name
,
'=0'
:
'zero'
,
'locale: _localeName'
,
'=1'
:
'one'
,
...
generateIntlMethodArgs
(
message
),
'=2'
:
'two'
,
];
'few'
:
'few'
,
'many'
:
'many'
,
'other'
:
'other'
};
final
List
<
String
>
pluralLogicArgs
=
<
String
>[];
for
(
final
String
pluralKey
in
pluralIds
.
keys
)
{
for
(
final
String
pluralKey
in
pluralIds
.
keys
)
{
final
RegExp
expRE
=
RegExp
(
'(
$pluralKey
){([^}]+)}'
);
final
RegExp
expRE
=
RegExp
(
'(
$pluralKey
)
\\
s*
{([^}]+)}'
);
final
RegExpMatch
match
=
expRE
.
firstMatch
(
easyMessage
);
final
RegExpMatch
match
=
expRE
.
firstMatch
(
easyMessage
);
if
(
match
!=
null
&&
match
.
groupCount
==
2
)
{
if
(
match
!=
null
&&
match
.
groupCount
==
2
)
{
String
argValue
=
match
.
group
(
2
);
String
argValue
=
match
.
group
(
2
);
for
(
final
Placeholder
placeholder
in
message
.
placeholders
)
{
for
(
final
Placeholder
placeholder
in
message
.
placeholders
)
{
if
(
placeholder
.
requiresFormatting
)
{
if
(
placeholder
!=
countPlaceholder
&&
placeholder
.
requiresFormatting
)
{
argValue
=
argValue
.
replaceAll
(
'#
${placeholder.name}
#'
,
'
\
${${placeholder.name}
String}'
);
argValue
=
argValue
.
replaceAll
(
'#
${placeholder.name}
#'
,
'
\
${${placeholder.name}
String}'
);
}
else
{
}
else
{
argValue
=
argValue
.
replaceAll
(
'#
${placeholder.name}
#'
,
'
\
${${placeholder.name}
}'
);
argValue
=
argValue
.
replaceAll
(
'#
${placeholder.name}
#'
,
'
\
${${placeholder.name}
}'
);
}
}
}
}
intlMethodArgs
.
add
(
"
${pluralIds[pluralKey]}
: '
$argValue
'"
);
pluralLogicArgs
.
add
(
"
${pluralIds[pluralKey]}
: '
$argValue
'"
);
}
}
final
List
<
String
>
parameters
=
message
.
placeholders
.
map
((
Placeholder
placeholder
)
{
final
String
placeholderType
=
placeholder
==
countPlaceholder
?
'int'
:
placeholder
.
type
;
return
'
$placeholderType
${placeholder.name}
'
;
}).
toList
();
final
String
comment
=
message
.
description
??
'No description provided in @
${message.resourceId}
'
;
return
pluralMethodTemplate
.
replaceAll
(
'@(comment)'
,
comment
)
.
replaceAll
(
'@(name)'
,
message
.
resourceId
)
.
replaceAll
(
'@(parameters)'
,
parameters
.
join
(
', '
))
.
replaceAll
(
'@(dateFormatting)'
,
generateDateFormattingLogic
(
message
))
.
replaceAll
(
'@(numberFormatting)'
,
generateNumberFormattingLogic
(
message
))
.
replaceAll
(
'@(count)'
,
countPlaceholder
.
name
)
.
replaceAll
(
'@(pluralLogicArgs)'
,
pluralLogicArgs
.
join
(
',
\n
'
))
.
replaceAll
(
'@(none)
\n
'
,
''
);
}
String
generateMethod
(
Message
message
,
AppResourceBundle
bundle
)
{
String
generateMessage
()
{
String
messageValue
=
bundle
.
translationFor
(
message
);
for
(
final
Placeholder
placeholder
in
message
.
placeholders
)
{
if
(
placeholder
.
requiresFormatting
)
{
messageValue
=
messageValue
.
replaceAll
(
'{
${placeholder.name}
}'
,
'
\
${${placeholder.name}
String}'
);
}
else
{
messageValue
=
messageValue
.
replaceAll
(
'{
${placeholder.name}
}'
,
'
\
${${placeholder.name}
}'
);
}
}
}
return
"'
$messageValue
'"
;
}
}
List
<
String
>
generatePluralMethodParameters
([
String
type
])
{
if
(
message
.
isPlural
)
{
return
message
.
placeholders
.
map
((
Placeholder
placeholder
)
{
return
generatePluralMethod
(
message
);
final
String
placeholderType
=
placeholder
==
countPlaceholder
?
'int'
:
(
type
??
placeholder
.
type
);
return
'
$placeholderType
${placeholder.name}
'
;
}).
toList
();
}
}
if
(
message
.
placeholdersRequireFormatting
)
{
if
(
message
.
placeholdersRequireFormatting
)
{
return
pluralF
ormatMethodTemplate
return
f
ormatMethodTemplate
.
replaceAll
(
'@(
methodN
ame)'
,
message
.
resourceId
)
.
replaceAll
(
'@(
n
ame)'
,
message
.
resourceId
)
.
replaceAll
(
'@(
methodParameters)'
,
generatePluralMethodParameters
(
).
join
(
', '
))
.
replaceAll
(
'@(
parameters)'
,
generateMethodParameters
(
message
).
join
(
', '
))
.
replaceAll
(
'@(dateFormatting)'
,
generateDateFormattingLogic
(
message
))
.
replaceAll
(
'@(dateFormatting)'
,
generateDateFormattingLogic
(
message
))
.
replaceAll
(
'@(numberFormatting)'
,
generateNumberFormattingLogic
(
message
))
.
replaceAll
(
'@(numberFormatting)'
,
generateNumberFormattingLogic
(
message
))
.
replaceAll
(
'@(innerMethodParameters)'
,
generatePluralMethodParameters
(
'Object'
).
join
(
', '
))
.
replaceAll
(
'@(message)'
,
generateMessage
())
.
replaceAll
(
'@(innerMethodArgs)'
,
generateInnerMethodArgs
(
message
).
join
(
', '
))
.
replaceAll
(
'@(none)
\n
'
,
''
);
.
replaceAll
(
'@(intlMethodArgs)'
,
intlMethodArgs
.
join
(
',
\n
'
));
}
}
return
pluralMethodTemplate
if
(
message
.
placeholders
.
isNotEmpty
)
{
.
replaceAll
(
'@(methodName)'
,
message
.
resourceId
)
return
methodTemplate
.
replaceAll
(
'@(methodParameters)'
,
generatePluralMethodParameters
().
join
(
', '
))
.
replaceAll
(
'@(name)'
,
message
.
resourceId
)
.
replaceAll
(
'@(dateFormatting)'
,
generateDateFormattingLogic
(
message
))
.
replaceAll
(
'@(parameters)'
,
generateMethodParameters
(
message
).
join
(
', '
))
.
replaceAll
(
'@(numberFormatting)'
,
generateNumberFormattingLogic
(
message
))
.
replaceAll
(
'@(message)'
,
generateMessage
());
.
replaceAll
(
'@(intlMethodArgs)'
,
intlMethodArgs
.
join
(
',
\n
'
));
}
return
getterTemplate
.
replaceAll
(
'@(name)'
,
message
.
resourceId
)
.
replaceAll
(
'@(message)'
,
generateMessage
());
}
String
generateClass
(
String
className
,
AppResourceBundle
bundle
,
Iterable
<
Message
>
messages
)
{
final
LocaleInfo
locale
=
bundle
.
locale
;
String
baseClassName
=
className
;
if
(
locale
.
countryCode
!=
null
)
{
baseClassName
=
'
$className${LocaleInfo.fromString(locale.languageCode).camelCase()}
'
;
}
final
Iterable
<
String
>
methods
=
messages
.
where
((
Message
message
)
=>
bundle
.
translationFor
(
message
)
!=
null
)
.
map
((
Message
message
)
=>
generateMethod
(
message
,
bundle
));
return
classTemplate
.
replaceAll
(
'@(language)'
,
describeLocale
(
locale
.
toString
()))
.
replaceAll
(
'@(baseClass)'
,
baseClassName
)
.
replaceAll
(
'@(class)'
,
'
$className${locale.camelCase()}
'
)
.
replaceAll
(
'@(localeName)'
,
locale
.
toString
())
.
replaceAll
(
'@(methods)'
,
methods
.
join
(
'
\n\n
'
));
}
String
generateBaseClassMethod
(
Message
message
)
{
final
String
comment
=
message
.
description
??
'No description provided in @
${message.resourceId}
'
;
if
(
message
.
placeholders
.
isNotEmpty
)
{
return
baseClassMethodTemplate
.
replaceAll
(
'@(comment)'
,
comment
)
.
replaceAll
(
'@(name)'
,
message
.
resourceId
)
.
replaceAll
(
'@(parameters)'
,
generateMethodParameters
(
message
).
join
(
', '
));
}
return
baseClassGetterTemplate
.
replaceAll
(
'@(comment)'
,
comment
)
.
replaceAll
(
'@(name)'
,
message
.
resourceId
);
}
String
generateLookupBody
(
AppResourceBundleCollection
allBundles
,
String
className
)
{
final
Iterable
<
String
>
switchClauses
=
allBundles
.
languages
.
map
((
String
language
)
{
final
Iterable
<
LocaleInfo
>
locales
=
allBundles
.
localesForLanguage
(
language
);
if
(
locales
.
length
==
1
)
{
return
switchClauseTemplate
.
replaceAll
(
'@(case)'
,
language
)
.
replaceAll
(
'@(class)'
,
'
$className${locales.first.camelCase()}
'
);
}
final
Iterable
<
LocaleInfo
>
localesWithCountryCodes
=
locales
.
where
((
LocaleInfo
locale
)
=>
locale
.
countryCode
!=
null
);
return
countryCodeSwitchTemplate
.
replaceAll
(
'@(languageCode)'
,
language
)
.
replaceAll
(
'@(class)'
,
'
$className${LocaleInfo.fromString(language).camelCase()}
'
)
.
replaceAll
(
'@(switchClauses)'
,
localesWithCountryCodes
.
map
((
LocaleInfo
locale
)
{
return
switchClauseTemplate
.
replaceAll
(
'@(case)'
,
locale
.
countryCode
)
.
replaceAll
(
'@(class)'
,
'
$className${locale.camelCase()}
'
);
}).
join
(
'
\n
'
));
});
return
switchClauses
.
join
(
'
\n
'
);
}
}
/// The localizations generation class used to generate the localizations
/// classes, as well as all pertinent Dart files required to internationalize a
/// Flutter application.
class
LocalizationsGenerator
{
class
LocalizationsGenerator
{
/// Creates an instance of the localizations generator class.
/// Creates an instance of the localizations generator class.
///
///
/// It takes in a [FileSystem] representation that the class will act upon.
/// It takes in a [FileSystem] representation that the class will act upon.
LocalizationsGenerator
(
this
.
_fs
);
LocalizationsGenerator
(
this
.
_fs
);
static
RegExp
arbFilenameLocaleRE
=
RegExp
(
r'^[^_]*_(\w+)\.arb$'
);
static
RegExp
arbFilenameRE
=
RegExp
(
r'(\w+)\.arb$'
);
final
file
.
FileSystem
_fs
;
final
file
.
FileSystem
_fs
;
Iterable
<
Message
>
_allMessages
;
AppResourceBundleCollection
_allBundles
;
/// The reference to the project's l10n directory.
/// The reference to the project's l10n directory.
///
///
...
@@ -404,7 +305,9 @@ class LocalizationsGenerator {
...
@@ -404,7 +305,9 @@ class LocalizationsGenerator {
List
<
LocaleInfo
>
_preferredSupportedLocales
;
List
<
LocaleInfo
>
_preferredSupportedLocales
;
/// The list of all arb path strings in [l10nDirectory].
/// The list of all arb path strings in [l10nDirectory].
final
List
<
String
>
arbPathStrings
=
<
String
>[];
List
<
String
>
get
arbPathStrings
{
return
_allBundles
.
bundles
.
map
((
AppResourceBundle
bundle
)
=>
bundle
.
file
.
path
).
toList
();
}
/// The supported language codes as found in the arb files located in
/// The supported language codes as found in the arb files located in
/// [l10nDirectory].
/// [l10nDirectory].
...
@@ -414,10 +317,6 @@ class LocalizationsGenerator {
...
@@ -414,10 +317,6 @@ class LocalizationsGenerator {
/// [l10nDirectory].
/// [l10nDirectory].
final
Set
<
LocaleInfo
>
supportedLocales
=
<
LocaleInfo
>{};
final
Set
<
LocaleInfo
>
supportedLocales
=
<
LocaleInfo
>{};
/// The class methods that will be generated in the localizations class
/// based on messages found in the template arb file.
final
List
<
String
>
classMethods
=
<
String
>[];
/// Initializes [l10nDirectory], [templateArbFile], [outputFile] and [className].
/// Initializes [l10nDirectory], [templateArbFile], [outputFile] and [className].
///
///
/// Throws an [L10nException] when a provided configuration is not allowed
/// Throws an [L10nException] when a provided configuration is not allowed
...
@@ -531,7 +430,9 @@ class LocalizationsGenerator {
...
@@ -531,7 +430,9 @@ class LocalizationsGenerator {
/// will take priority over the other locales.
/// will take priority over the other locales.
@visibleForTesting
@visibleForTesting
void
setPreferredSupportedLocales
(
String
inputLocales
)
{
void
setPreferredSupportedLocales
(
String
inputLocales
)
{
if
(
inputLocales
!=
null
)
{
if
(
inputLocales
==
null
||
inputLocales
.
trim
().
isEmpty
)
{
_preferredSupportedLocales
=
const
<
LocaleInfo
>[];
}
else
{
final
List
<
dynamic
>
preferredLocalesStringList
=
json
.
decode
(
inputLocales
)
as
List
<
dynamic
>;
final
List
<
dynamic
>
preferredLocalesStringList
=
json
.
decode
(
inputLocales
)
as
List
<
dynamic
>;
_preferredSupportedLocales
=
preferredLocalesStringList
.
map
((
dynamic
localeString
)
{
_preferredSupportedLocales
=
preferredLocalesStringList
.
map
((
dynamic
localeString
)
{
if
(
localeString
.
runtimeType
!=
String
)
{
if
(
localeString
.
runtimeType
!=
String
)
{
...
@@ -542,70 +443,6 @@ class LocalizationsGenerator {
...
@@ -542,70 +443,6 @@ class LocalizationsGenerator {
}
}
}
}
/// Scans [l10nDirectory] for arb files and parses them for language and locale
/// information.
void
parseArbFiles
()
{
final
List
<
File
>
fileSystemEntityList
=
l10nDirectory
.
listSync
()
.
whereType
<
File
>()
.
toList
();
final
List
<
LocaleInfo
>
localeInfoList
=
<
LocaleInfo
>[];
for
(
final
File
file
in
fileSystemEntityList
)
{
final
String
filePath
=
file
.
path
;
if
(
arbFilenameRE
.
hasMatch
(
filePath
))
{
final
Map
<
String
,
dynamic
>
arbContents
=
json
.
decode
(
file
.
readAsStringSync
())
as
Map
<
String
,
dynamic
>;
String
localeString
=
arbContents
[
'@@locale'
]
as
String
;
if
(
localeString
==
null
)
{
final
RegExpMatch
arbFileMatch
=
arbFilenameLocaleRE
.
firstMatch
(
filePath
);
if
(
arbFileMatch
==
null
)
{
throw
L10nException
(
"The following .arb file's locale could not be determined:
\n
"
'
$filePath
\n
'
"Make sure that the locale is specified in the '@@locale' "
'property or as part of the filename (e.g. file_en.arb)'
);
}
localeString
=
arbFilenameLocaleRE
.
firstMatch
(
filePath
)[
1
];
}
arbPathStrings
.
add
(
filePath
);
final
LocaleInfo
localeInfo
=
LocaleInfo
.
fromString
(
localeString
);
if
(
localeInfoList
.
contains
(
localeInfo
))
throw
L10nException
(
'Multiple arb files with the same locale detected.
\n
'
'Ensure that there is exactly one arb file for each locale.'
);
localeInfoList
.
add
(
localeInfo
);
}
}
arbPathStrings
.
sort
();
localeInfoList
.
sort
();
supportedLanguageCodes
.
addAll
(
localeInfoList
.
map
((
LocaleInfo
localeInfo
)
{
return
"'
${localeInfo.languageCode}
'"
;
}));
if
(
preferredSupportedLocales
!=
null
)
{
for
(
final
LocaleInfo
preferredLocale
in
preferredSupportedLocales
)
{
if
(!
localeInfoList
.
contains
(
preferredLocale
))
{
throw
L10nException
(
"The preferred supported locale, '
$preferredLocale
', cannot be "
'added. Please make sure that there is a corresponding arb file '
'with translations for the locale, or remove the locale from the '
'preferred supported locale list if there is no intent to support '
'it.'
);
}
localeInfoList
.
removeWhere
((
LocaleInfo
localeInfo
)
=>
localeInfo
==
preferredLocale
);
}
localeInfoList
.
insertAll
(
0
,
preferredSupportedLocales
);
}
supportedLocales
.
addAll
(
localeInfoList
);
}
static
bool
_isValidGetterAndMethodName
(
String
name
)
{
static
bool
_isValidGetterAndMethodName
(
String
name
)
{
// Public Dart method name must not start with an underscore
// Public Dart method name must not start with an underscore
if
(
name
[
0
]
==
'_'
)
if
(
name
[
0
]
==
'_'
)
...
@@ -622,83 +459,78 @@ class LocalizationsGenerator {
...
@@ -622,83 +459,78 @@ class LocalizationsGenerator {
return
true
;
return
true
;
}
}
static
String
_genSupportedLocaleProperty
(
Set
<
LocaleInfo
>
supportedLocales
)
{
// Load _allMessages from templateArbFile and _allBundles from all of the ARB
const
String
prefix
=
'static const List<Locale> supportedLocales = <Locale>[
\n
Locale('
;
// files in l10nDirectory. Also initialized: supportedLocales.
const
String
suffix
=
'),
\n
];'
;
void
loadResources
()
{
final
AppResourceBundle
templateBundle
=
AppResourceBundle
(
templateArbFile
);
String
resultingProperty
=
prefix
;
_allMessages
=
templateBundle
.
resourceIds
.
map
((
String
id
)
=>
Message
(
templateBundle
.
resources
,
id
));
for
(
final
LocaleInfo
locale
in
supportedLocales
)
{
for
(
final
String
resourceId
in
templateBundle
.
resourceIds
)
final
String
languageCode
=
locale
.
languageCode
;
if
(!
_isValidGetterAndMethodName
(
resourceId
))
{
final
String
countryCode
=
locale
.
countryCode
;
throw
L10nException
(
'Invalid ARB resource name "
$resourceId
" in
$templateArbFile
.
\n
'
'Resources names must be valid Dart method names: they have to be '
'camel case, cannot start with a number or underscore, and cannot '
'contain non-alphanumeric characters.'
);
}
resultingProperty
+=
"'
$languageCode
'"
;
_allBundles
=
AppResourceBundleCollection
(
l10nDirectory
);
if
(
countryCode
!=
null
)
resultingProperty
+=
", '
$countryCode
'"
;
resultingProperty
+=
'),
\n
Locale('
;
}
resultingProperty
=
resultingProperty
.
substring
(
0
,
resultingProperty
.
length
-
'),
\n
Locale('
.
length
);
resultingProperty
+=
suffix
;
return
resultingProperty
;
final
List
<
LocaleInfo
>
allLocales
=
List
<
LocaleInfo
>.
from
(
_allBundles
.
locales
);
}
for
(
final
LocaleInfo
preferredLocale
in
preferredSupportedLocales
)
{
final
int
index
=
allLocales
.
indexOf
(
preferredLocale
);
/// Generates the methods for the localizations class.
if
(
index
==
-
1
)
{
///
/// The method parses [templateArbFile] and uses its resource ids as the
/// Dart method and getter names. It then uses each resource id's
/// corresponding resource value to figure out how to define these getters.
///
/// For example, a message with plurals will be handled differently from
/// a simple, singular message.
///
/// Throws an [L10nException] when a provided configuration is not allowed
/// by [LocalizationsGenerator].
///
/// Throws a [FileSystemException] when a file operation necessary for setting
/// up the [LocalizationsGenerator] cannot be completed.
///
/// Throws a [FormatException] when parsing the arb file is unsuccessful.
void
generateClassMethods
()
{
Map
<
String
,
dynamic
>
bundle
;
try
{
bundle
=
json
.
decode
(
templateArbFile
.
readAsStringSync
())
as
Map
<
String
,
dynamic
>;
}
on
FileSystemException
catch
(
e
)
{
throw
FileSystemException
(
'Unable to read input arb file:
$e
'
);
}
on
FormatException
catch
(
e
)
{
throw
FormatException
(
'Unable to parse arb file:
$e
'
);
}
final
List
<
String
>
sortedArbKeys
=
bundle
.
keys
.
toList
()..
sort
();
for
(
final
String
key
in
sortedArbKeys
)
{
if
(
key
.
startsWith
(
'@'
))
continue
;
if
(!
_isValidGetterAndMethodName
(
key
))
{
throw
L10nException
(
throw
L10nException
(
'Invalid key format:
$key
\n
It has to be in camel case, cannot start '
"The preferred supported locale, '
$preferredLocale
', cannot be "
'with a number or underscore, and cannot contain non-alphanumeric characters.'
'added. Please make sure that there is a corresponding ARB file '
'with translations for the locale, or remove the locale from the '
'preferred supported locale list.'
);
);
}
}
allLocales
.
removeAt
(
index
);
final
Message
message
=
Message
(
bundle
,
key
);
allLocales
.
insertAll
(
0
,
preferredSupportedLocales
);
if
(
message
.
isPlural
)
classMethods
.
add
(
generatePluralMethod
(
message
));
else
classMethods
.
add
(
genSimpleMethod
(
message
));
}
}
supportedLocales
.
addAll
(
allLocales
);
}
}
/// Generates a file that contains the localizations class and the
// Generate the AppLocalizations class, its LocalizationsDelegate subclass.
/// LocalizationsDelegate class.
String
generateCode
()
{
void
generateOutputFile
()
{
final
String
directory
=
path
.
basename
(
l10nDirectory
.
path
);
final
String
directory
=
path
.
basename
(
l10nDirectory
.
path
);
final
String
outputFileName
=
path
.
basename
(
outputFile
.
path
);
final
String
outputFileName
=
path
.
basename
(
outputFile
.
path
);
outputFile
.
writeAsStringSync
(
defaultFileTemplate
final
Iterable
<
String
>
supportedLocalesCode
=
supportedLocales
.
map
((
LocaleInfo
locale
)
{
.
replaceAll
(
'@(className)'
,
className
)
final
String
country
=
locale
.
countryCode
;
.
replaceAll
(
'@(classMethods)'
,
classMethods
.
join
(
'
\n
'
))
final
String
countryArg
=
country
==
null
?
''
:
',
$country
'
;
.
replaceAll
(
'@(importFile)'
,
'
$directory
/
$outputFileName
'
)
return
'Locale(
\'
${locale.languageCode}$countryArg
\'
)'
;
.
replaceAll
(
'@(supportedLocales)'
,
_genSupportedLocaleProperty
(
supportedLocales
))
});
.
replaceAll
(
'@(supportedLanguageCodes)'
,
supportedLanguageCodes
.
toList
().
join
(
', '
))
final
Set
<
String
>
supportedLanguageCodes
=
Set
<
String
>.
from
(
_allBundles
.
locales
.
map
<
String
>((
LocaleInfo
locale
)
=>
'
\'
${locale.languageCode}
\'
'
)
);
);
final
StringBuffer
allMessagesClasses
=
StringBuffer
();
final
List
<
LocaleInfo
>
allLocales
=
_allBundles
.
locales
.
toList
()..
sort
();
for
(
final
LocaleInfo
locale
in
allLocales
)
{
allMessagesClasses
.
writeln
();
allMessagesClasses
.
writeln
(
generateClass
(
className
,
_allBundles
.
bundleFor
(
locale
),
_allMessages
)
);
}
final
String
lookupBody
=
generateLookupBody
(
_allBundles
,
className
);
return
fileTemplate
.
replaceAll
(
'@(class)'
,
className
)
.
replaceAll
(
'@(methods)'
,
_allMessages
.
map
(
generateBaseClassMethod
).
join
(
'
\n
'
))
.
replaceAll
(
'@(importFile)'
,
'
$directory
/
$outputFileName
'
)
.
replaceAll
(
'@(supportedLocales)'
,
supportedLocalesCode
.
join
(
',
\n
'
))
.
replaceAll
(
'@(supportedLanguageCodes)'
,
supportedLanguageCodes
.
join
(
', '
))
.
replaceAll
(
'@(allMessagesClasses)'
,
allMessagesClasses
.
toString
().
trim
())
.
replaceAll
(
'@(lookupName)'
,
'_lookup
$className
'
)
.
replaceAll
(
'@(lookupBody)'
,
lookupBody
);
}
void
writeOutputFile
()
{
outputFile
.
writeAsStringSync
(
generateCode
());
}
}
}
}
dev/tools/localization/gen_l10n_templates.dart
View file @
13e1336e
...
@@ -2,80 +2,29 @@
...
@@ -2,80 +2,29 @@
// Use of this source code is governed by a BSD-style license that can be
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// found in the LICENSE file.
const
String
getterMethodTemplate
=
'''
const
String
fileTemplate
=
'''
String get @(methodName) {
return Intl.message(
@(message),
locale: _localeName,
@(intlMethodArgs)
);
}
'''
;
const
String
simpleMethodTemplate
=
'''
String @(methodName)(@(methodParameters)) {
return Intl.message(
@(message),
locale: _localeName,
@(intlMethodArgs)
);
}
'''
;
const
String
formatMethodTemplate
=
'''
String @(methodName)(@(methodParameters)) {@(dateFormatting)@(numberFormatting)
String @(methodName)(@(innerMethodParameters)) {
return Intl.message(
@(message),
locale: _localeName,
@(intlMethodArgs)
);
}
return @(methodName)(@(innerMethodArgs));
}
'''
;
const
String
pluralMethodTemplate
=
'''
String @(methodName)(@(methodParameters)) {@(dateFormatting)@(numberFormatting)
return Intl.plural(
@(intlMethodArgs)
);
}
'''
;
const
String
pluralFormatMethodTemplate
=
'''
String @(methodName)(@(methodParameters)) {@(dateFormatting)@(numberFormatting)
String @(methodName)(@(innerMethodParameters)) {
return Intl.plural(
@(intlMethodArgs)
);
}
return @(methodName)(@(innerMethodArgs));
}
'''
;
const
String
defaultFileTemplate
=
'''
import '
dart:
async
';
import '
dart:
async
';
import '
package:
flutter
/
foundation
.
dart
';
import '
package:
flutter
/
widgets
.
dart
';
import '
package:
flutter
/
widgets
.
dart
';
import '
package:
flutter_localizations
/
flutter_localizations
.
dart
';
import '
package:
flutter_localizations
/
flutter_localizations
.
dart
';
import '
package:
intl
/
intl
.
dart
';
import '
package:
intl
/
intl
.
dart
'
as intl
;
import '
messages_all
.
dart
';
// ignore_for_file: unnecessary_brace_in_string_interps
/// Callers can lookup localized strings with an instance of @(class
Name
) returned
/// Callers can lookup localized strings with an instance of @(class) returned
/// by `@(class
Name
).of(context)`.
/// by `@(class).of(context)`.
///
///
/// Applications need to include `@(class
Name).delegate()` in their app
'
s
/// Applications need to include `@(class
).delegate()` in their app
\
'
s
/// localizationDelegates list, and the locales they support in the app's
/// localizationDelegates list, and the locales they support in the app
\
'
s
/// supportedLocales list. For example:
/// supportedLocales list. For example:
///
///
/// ```
/// ```
/// import '
@
(
importFile
)
';
/// import '
@
(
importFile
)
';
///
///
/// return MaterialApp(
/// return MaterialApp(
/// localizationsDelegates: @(class
Name
).localizationsDelegates,
/// localizationsDelegates: @(class).localizationsDelegates,
/// supportedLocales: @(class
Name
).supportedLocales,
/// supportedLocales: @(class).supportedLocales,
/// home: MyApplicationHome(),
/// home: MyApplicationHome(),
/// );
/// );
/// ```
/// ```
...
@@ -113,26 +62,18 @@ import 'messages_all.dart';
...
@@ -113,26 +62,18 @@ import 'messages_all.dart';
/// Select and expand the newly-created Localizations item then, for each
/// Select and expand the newly-created Localizations item then, for each
/// locale your application supports, add a new item and select the locale
/// locale your application supports, add a new item and select the locale
/// you wish to add from the pop-up menu in the Value field. This list should
/// you wish to add from the pop-up menu in the Value field. This list should
/// be consistent with the languages listed in the @(class
Name
).supportedLocales
/// be consistent with the languages listed in the @(class).supportedLocales
/// property.
/// property.
abstract class @(class) {
// ignore_for_file: unnecessary_brace_in_string_interps
@(class)(String locale) : assert(locale != null), _localeName = intl.Intl.canonicalizedLocale(locale.toString());
class
@(
className
)
{
@
(
className
)(
Locale
locale
)
:
_localeName
=
Intl
.
canonicalizedLocale
(
locale
.
toString
());
final String _localeName;
final String _localeName;
static
Future
<
@
(
className
)>
load
(
Locale
locale
)
{
static @(class) of(BuildContext context) {
return
initializeMessages
(
locale
.
toString
())
return Localizations.of<@(class)>(context, @(class));
.
then
<
@
(
className
)>((
_
)
=>
@
(
className
)(
locale
));
}
static
@
(
className
)
of
(
BuildContext
context
)
{
return
Localizations
.
of
<
@
(
className
)>(
context
,
@
(
className
));
}
}
static
const
LocalizationsDelegate
<
@
(
class
Name
)>
delegate
=
_
@
(
className
)
Delegate
();
static const LocalizationsDelegate<@(class
)> delegate = _@(class
)Delegate();
/// A list of this localizations delegate along with the default localizations
/// A list of this localizations delegate along with the default localizations
/// delegates.
/// delegates.
...
@@ -152,21 +93,104 @@ class @(className) {
...
@@ -152,21 +93,104 @@ class @(className) {
];
];
/// A list of this localizations delegate'
s
supported
locales
.
/// A list of this localizations delegate'
s
supported
locales
.
@
(
supportedLocales
)
static
const
List
<
Locale
>
supportedLocales
=
<
Locale
>[
@
(
supportedLocales
)
];
@
(
classMethods
)
@
(
methods
)}
}
class
_
@
(
class
Name
)
Delegate
extends
LocalizationsDelegate
<
@
(
className
)>
{
class
_
@
(
class
)
Delegate
extends
LocalizationsDelegate
<
@
(
class
)>
{
const
_
@
(
class
Name
)
Delegate
();
const
_
@
(
class
)
Delegate
();
@override
@override
Future
<
@
(
className
)>
load
(
Locale
locale
)
=>
@
(
className
).
load
(
locale
);
Future
<
@
(
class
)>
load
(
Locale
locale
)
{
return
SynchronousFuture
<
@
(
class
)>(@(
lookupName
)(
locale
));
}
@override
@override
bool
isSupported
(
Locale
locale
)
=>
<
String
>[
@
(
supportedLanguageCodes
)].
contains
(
locale
.
languageCode
);
bool
isSupported
(
Locale
locale
)
=>
<
String
>[
@
(
supportedLanguageCodes
)].
contains
(
locale
.
languageCode
);
@override
@override
bool
shouldReload
(
_
@
(
class
Name
)
Delegate
old
)
=>
false
;
bool
shouldReload
(
_
@
(
class
)
Delegate
old
)
=>
false
;
}
}
@
(
allMessagesClasses
)
@
(
class
)
@(
lookupName
)(
Locale
locale
)
{
switch
(
locale
.
languageCode
)
{
@
(
lookupBody
)
}
assert
(
false
,
'@(class).delegate failed to load unsupported locale "
\
$locale
"'
);
return
null
;
}
''';
const String numberFormatTemplate = '''
final
intl
.
NumberFormat
@
(
placeholder
)
NumberFormat
=
intl
.
NumberFormat
.
@
(
format
)(
locale:
_localeName
,
@
(
parameters
)
);
final
String
@
(
placeholder
)
String
=
@
(
placeholder
)
NumberFormat
.
format
(
@
(
placeholder
));
''';
const String dateFormatTemplate = '''
final
intl
.
DateFormat
@
(
placeholder
)
DateFormat
=
intl
.
DateFormat
.
@
(
format
)(
_localeName
);
final
String
@
(
placeholder
)
String
=
@
(
placeholder
)
DateFormat
.
format
(
@
(
placeholder
));
''';
const String getterTemplate = '''
@override
String
get
@
(
name
)
=>
@
(
message
);
''';
const String methodTemplate = '''
@override
String
@
(
name
)(
@
(
parameters
))
{
return
@
(
message
);
}
''';
const String formatMethodTemplate = '''
@override
String
@
(
name
)(
@
(
parameters
))
{
@
(
dateFormatting
)
@
(
numberFormatting
)
return
@
(
message
);
}
''';
const String pluralMethodTemplate = '''
@override
String
@
(
name
)(
@
(
parameters
))
{
@
(
dateFormatting
)
@
(
numberFormatting
)
return
intl
.
Intl
.
pluralLogic
(
@
(
count
),
locale:
_localeName
,
@
(
pluralLogicArgs
),
);
}
''';
const String classTemplate = '''
/// The translations for @(language) (`@(localeName)`).
class
@(
class
)
extends
@
(
baseClass
)
{
@
(
class
)([
String
locale
=
'@(localeName)'
])
:
super
(
locale
);
@
(
methods
)
}
''';
const String baseClassGetterTemplate = '''
// @(comment)
String
get
@
(
name
);
''';
''';
const String baseClassMethodTemplate = '''
// @(comment)
String
@
(
name
)(
@
(
parameters
));
''';
const String switchClauseTemplate = '''
case
'@(case)'
:
return
@
(
class
)();''';
const
String
countryCodeSwitchTemplate
=
'''case '
@
(
languageCode
)
': {
switch (locale.countryCode) {
@(switchClauses)
}
return @(class)();
}'''
;
dev/tools/localization/gen_l10n_types.dart
View file @
13e1336e
...
@@ -2,12 +2,149 @@
...
@@ -2,12 +2,149 @@
// Use of this source code is governed by a BSD-style license that can be
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// found in the LICENSE file.
import
'dart:convert'
;
import
'dart:io'
;
import
'localizations_utils.dart'
;
// 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
>
_validDateFormats
=
<
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'
,
};
// The set of number formats that can be automatically localized.
//
// The localizations generation tool makes use of the intl library's
// NumberFormat class to properly format numbers based on the locale, the
// desired format, as well as the passed in number. For example, using
// DateFormat.compactLong("en_US").format(1200000) results
// in the string "1.2 million".
//
// Since the tool generates code that uses NumberFormat'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/NumberFormat-class.html>
const
Set
<
String
>
_validNumberFormats
=
<
String
>{
'compact'
,
'compactCurrency'
,
'compactSimpleCurrency'
,
'compactLong'
,
'currency'
,
'decimalPattern'
,
'decimalPercentPattern'
,
'percentPattern'
,
'scientificPattern'
,
'simpleCurrency'
,
};
// The names of the NumberFormat factory constructors which have named
// parameters rather than positional parameters.
//
// This helps the tool correctly generate number formmatting code correctly.
//
// Example of code that uses named parameters:
// final NumberFormat format = NumberFormat.compact(
// locale: _localeName,
// );
//
// Example of code that uses positional parameters:
// final NumberFormat format = NumberFormat.scientificPattern(_localeName);
const
Set
<
String
>
_numberFormatsWithNamedParameters
=
<
String
>{
'compact'
,
'compactCurrency'
,
'compactSimpleCurrency'
,
'compactLong'
,
'currency'
,
'decimalPercentPattern'
,
'simpleCurrency'
,
};
class
L10nException
implements
Exception
{
class
L10nException
implements
Exception
{
L10nException
(
this
.
message
);
L10nException
(
this
.
message
);
final
String
message
;
final
String
message
;
}
}
// One optional named parameter to be used by a NumberFormat.
//
// Some of the NumberFormat factory constructors have optional named parameters.
// For example NumberFormat.compactCurrency has a decimalDigits parameter that
// specifies the number of decimal places to use when formatting.
//
// Optional parameters for NumberFormat placeholders are specified as a
// JSON map value for optionalParameters in a resource's "@" ARB file entry:
//
// "@myResourceId": {
// "placeholders": {
// "myNumberPlaceholder": {
// "type": "double",
// "format": "compactCurrency",
// "optionalParameters": {
// "decimalDigits": 2
// }
// }
// }
// }
class
OptionalParameter
{
class
OptionalParameter
{
const
OptionalParameter
(
this
.
name
,
this
.
value
)
:
assert
(
name
!=
null
),
assert
(
value
!=
null
);
const
OptionalParameter
(
this
.
name
,
this
.
value
)
:
assert
(
name
!=
null
),
assert
(
value
!=
null
);
...
@@ -15,6 +152,39 @@ class OptionalParameter {
...
@@ -15,6 +152,39 @@ class OptionalParameter {
final
Object
value
;
final
Object
value
;
}
}
// One message parameter: one placeholder from an @foo entry in the template ARB file.
//
// Placeholders are specified as a JSON map with one entry for each placeholder.
// One placeholder must be specified for each message "{parameter}".
// Each placeholder entry is also a JSON map. If the map is empty, the placeholder
// is assumed to be an Object value whose toString() value will be displayed.
// For example:
//
// "greeting": "{hello} {world}",
// "@greeting": {
// "description": "A message with a two parameters",
// "placeholders": {
// "hello": {},
// "world": {}
// }
// }
//
// Each placeholder can optionally specify a valid Dart type. If the type
// is NumberFormat or DateFormat then a format which matches one of the
// type's factory constructors can also be specified. In this example the
// date placeholder is to be formated with DateFormat.yMMMMd:
//
// "helloWorldOn": "Hello World on {date}",
// "@helloWorldOn": {
// "description": "A message with a date parameter",
// "placeholders": {
// "date": {
// "type": "DateTime",
// "format": "yMMMMd"
// }
// }
// }
//
class
Placeholder
{
class
Placeholder
{
Placeholder
(
this
.
resourceId
,
this
.
name
,
Map
<
String
,
dynamic
>
attributes
)
Placeholder
(
this
.
resourceId
,
this
.
name
,
Map
<
String
,
dynamic
>
attributes
)
:
assert
(
resourceId
!=
null
),
:
assert
(
resourceId
!=
null
),
...
@@ -33,7 +203,10 @@ class Placeholder {
...
@@ -33,7 +203,10 @@ class Placeholder {
bool
get
requiresFormatting
=>
<
String
>[
'DateTime'
,
'double'
,
'int'
,
'num'
].
contains
(
type
);
bool
get
requiresFormatting
=>
<
String
>[
'DateTime'
,
'double'
,
'int'
,
'num'
].
contains
(
type
);
bool
get
isNumber
=>
<
String
>[
'double'
,
'int'
,
'num'
].
contains
(
type
);
bool
get
isNumber
=>
<
String
>[
'double'
,
'int'
,
'num'
].
contains
(
type
);
bool
get
hasValidNumberFormat
=>
_validNumberFormats
.
contains
(
format
);
bool
get
hasNumberFormatWithParameters
=>
_numberFormatsWithNamedParameters
.
contains
(
format
);
bool
get
isDate
=>
'DateTime'
==
type
;
bool
get
isDate
=>
'DateTime'
==
type
;
bool
get
hasValidDateFormat
=>
_validDateFormats
.
contains
(
format
);
static
String
_stringAttribute
(
static
String
_stringAttribute
(
String
resourceId
,
String
resourceId
,
...
@@ -75,6 +248,21 @@ class Placeholder {
...
@@ -75,6 +248,21 @@ class Placeholder {
}
}
}
}
// One translation: one pair of foo,@foo entries from the template ARB file.
//
// The template ARB file must contain an entry called @myResourceId for each
// message named myResourceId. The @ entry describes message parameters
// called "placeholders" and can include an optional description.
// Here's a simple example message with no parameters:
//
// "helloWorld": "Hello World",
// "@helloWorld": {
// "description": "The conventional newborn programmer greeting"
// }
//
// The value of this Message is "Hello World". The Message's value is the
// localized string to be shown for the template ARB file's locale.
// The docs for the Placeholder explain how placeholder entries are defined.
class
Message
{
class
Message
{
Message
(
Map
<
String
,
dynamic
>
bundle
,
this
.
resourceId
)
Message
(
Map
<
String
,
dynamic
>
bundle
,
this
.
resourceId
)
:
assert
(
bundle
!=
null
),
:
assert
(
bundle
!=
null
),
...
@@ -169,3 +357,90 @@ class Message {
...
@@ -169,3 +357,90 @@ class Message {
}).
toList
();
}).
toList
();
}
}
}
}
// Represents the contents of one ARB file.
class
AppResourceBundle
{
factory
AppResourceBundle
(
File
file
)
{
assert
(
file
!=
null
);
// Assuming that the caller has verified that the file exists and is readable.
final
Map
<
String
,
dynamic
>
resources
=
json
.
decode
(
file
.
readAsStringSync
())
as
Map
<
String
,
dynamic
>;
String
localeString
=
resources
[
'@@locale'
]
as
String
;
if
(
localeString
==
null
)
{
final
RegExp
filenameRE
=
RegExp
(
r'^[^_]*_(\w+)\.arb$'
);
final
RegExpMatch
match
=
filenameRE
.
firstMatch
(
file
.
path
);
localeString
=
match
==
null
?
null
:
match
[
1
];
}
if
(
localeString
==
null
)
{
throw
L10nException
(
"The following .arb file's locale could not be determined:
\n
"
'
${file.path}
\n
'
"Make sure that the locale is specified in the file's '@@locale' "
'property or as part of the filename (e.g. file_en.arb)'
);
}
final
Iterable
<
String
>
ids
=
resources
.
keys
.
where
((
String
key
)
=>
!
key
.
startsWith
(
'@'
));
return
AppResourceBundle
.
_
(
file
,
LocaleInfo
.
fromString
(
localeString
),
resources
,
ids
);
}
const
AppResourceBundle
.
_
(
this
.
file
,
this
.
locale
,
this
.
resources
,
this
.
resourceIds
);
final
File
file
;
final
LocaleInfo
locale
;
final
Map
<
String
,
dynamic
>
resources
;
final
Iterable
<
String
>
resourceIds
;
String
translationFor
(
Message
message
)
=>
resources
[
message
.
resourceId
]
as
String
;
@override
String
toString
()
{
return
'AppResourceBundle(
$locale
,
${file.path}
)'
;
}
}
// Represents all of the ARB files in [directory] as [AppResourceBundle]s.
class
AppResourceBundleCollection
{
factory
AppResourceBundleCollection
(
Directory
directory
)
{
assert
(
directory
!=
null
);
// Assuming that the caller has verified that the directory is readable.
final
RegExp
filenameRE
=
RegExp
(
r'(\w+)\.arb$'
);
final
Map
<
LocaleInfo
,
AppResourceBundle
>
localeToBundle
=
<
LocaleInfo
,
AppResourceBundle
>{};
final
Map
<
String
,
List
<
LocaleInfo
>>
languageToLocales
=
<
String
,
List
<
LocaleInfo
>>{};
final
List
<
File
>
files
=
directory
.
listSync
().
whereType
<
File
>().
toList
()..
sort
(
sortFilesByPath
);
for
(
final
File
file
in
files
)
{
if
(
filenameRE
.
hasMatch
(
file
.
path
))
{
final
AppResourceBundle
bundle
=
AppResourceBundle
(
file
);
if
(
localeToBundle
[
bundle
.
locale
]
!=
null
)
{
throw
L10nException
(
"Multiple arb files with the same '
${bundle.locale}
' locale detected.
\n
"
'Ensure that there is exactly one arb file for each locale.'
);
}
localeToBundle
[
bundle
.
locale
]
=
bundle
;
languageToLocales
[
bundle
.
locale
.
languageCode
]
??=
<
LocaleInfo
>[];
languageToLocales
[
bundle
.
locale
.
languageCode
].
add
(
bundle
.
locale
);
}
}
return
AppResourceBundleCollection
.
_
(
directory
,
localeToBundle
,
languageToLocales
);
}
const
AppResourceBundleCollection
.
_
(
this
.
_directory
,
this
.
_localeToBundle
,
this
.
_languageToLocales
);
final
Directory
_directory
;
final
Map
<
LocaleInfo
,
AppResourceBundle
>
_localeToBundle
;
final
Map
<
String
,
List
<
LocaleInfo
>>
_languageToLocales
;
Iterable
<
LocaleInfo
>
get
locales
=>
_localeToBundle
.
keys
;
Iterable
<
AppResourceBundle
>
get
bundles
=>
_localeToBundle
.
values
;
AppResourceBundle
bundleFor
(
LocaleInfo
locale
)
=>
_localeToBundle
[
locale
];
Iterable
<
String
>
get
languages
=>
_languageToLocales
.
keys
;
Iterable
<
LocaleInfo
>
localesForLanguage
(
String
language
)
=>
_languageToLocales
[
language
]
??
<
LocaleInfo
>[];
@override
String
toString
()
{
return
'AppResourceBundleCollection(
${_directory.path}
,
${locales.length}
locales)'
;
}
}
dev/tools/test/localization/gen_l10n_test.dart
View file @
13e1336e
...
@@ -25,7 +25,6 @@ const String singleMessageArbFileString = '''
...
@@ -25,7 +25,6 @@ const String singleMessageArbFileString = '''
"description": "Title for the application"
"description": "Title for the application"
}
}
}'''
;
}'''
;
const
String
esArbFileName
=
'app_es.arb'
;
const
String
esArbFileName
=
'app_es.arb'
;
const
String
singleEsMessageArbFileString
=
'''
const
String
singleEsMessageArbFileString
=
'''
{
{
...
@@ -48,10 +47,11 @@ void _standardFlutterDirectoryL10nSetup(FileSystem fs) {
...
@@ -48,10 +47,11 @@ void _standardFlutterDirectoryL10nSetup(FileSystem fs) {
void
main
(
)
{
void
main
(
)
{
MemoryFileSystem
fs
;
MemoryFileSystem
fs
;
setUp
(()
{
setUp
(()
async
{
fs
=
MemoryFileSystem
(
fs
=
MemoryFileSystem
(
style:
Platform
.
isWindows
?
FileSystemStyle
.
windows
:
FileSystemStyle
.
posix
style:
Platform
.
isWindows
?
FileSystemStyle
.
windows
:
FileSystemStyle
.
posix
);
);
await
precacheLanguageAndRegionTags
();
});
});
group
(
'Setters'
,
()
{
group
(
'Setters'
,
()
{
...
@@ -233,7 +233,7 @@ void main() {
...
@@ -233,7 +233,7 @@ void main() {
});
});
});
});
group
(
'
parseArbFil
es'
,
()
{
group
(
'
loadResourc
es'
,
()
{
test
(
'correctly initializes supportedLocales and supportedLanguageCodes properties'
,
()
{
test
(
'correctly initializes supportedLocales and supportedLanguageCodes properties'
,
()
{
_standardFlutterDirectoryL10nSetup
(
fs
);
_standardFlutterDirectoryL10nSetup
(
fs
);
...
@@ -246,7 +246,7 @@ void main() {
...
@@ -246,7 +246,7 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator
.
parseArbFil
es
();
generator
.
loadResourc
es
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
fail
(
'Setting language and locales should not fail:
\n
$e
'
);
fail
(
'Setting language and locales should not fail:
\n
$e
'
);
}
}
...
@@ -275,7 +275,7 @@ void main() {
...
@@ -275,7 +275,7 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator
.
parseArbFil
es
();
generator
.
loadResourc
es
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
fail
(
'Setting language and locales should not fail:
\n
$e
'
);
fail
(
'Setting language and locales should not fail:
\n
$e
'
);
}
}
...
@@ -306,7 +306,7 @@ void main() {
...
@@ -306,7 +306,7 @@ void main() {
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
preferredSupportedLocaleString:
preferredSupportedLocaleString
,
preferredSupportedLocaleString:
preferredSupportedLocaleString
,
);
);
generator
.
parseArbFil
es
();
generator
.
loadResourc
es
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
fail
(
'Setting language and locales should not fail:
\n
$e
'
);
fail
(
'Setting language and locales should not fail:
\n
$e
'
);
}
}
...
@@ -340,7 +340,7 @@ void main() {
...
@@ -340,7 +340,7 @@ void main() {
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
preferredSupportedLocaleString:
preferredSupportedLocaleString
,
preferredSupportedLocaleString:
preferredSupportedLocaleString
,
);
);
generator
.
parseArbFil
es
();
generator
.
loadResourc
es
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
expect
(
expect
(
e
.
message
,
e
.
message
,
...
@@ -381,7 +381,7 @@ void main() {
...
@@ -381,7 +381,7 @@ void main() {
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
preferredSupportedLocaleString:
preferredSupportedLocaleString
,
preferredSupportedLocaleString:
preferredSupportedLocaleString
,
);
);
generator
.
parseArbFil
es
();
generator
.
loadResourc
es
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
expect
(
expect
(
e
.
message
,
e
.
message
,
...
@@ -417,7 +417,7 @@ void main() {
...
@@ -417,7 +417,7 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator
.
parseArbFil
es
();
generator
.
loadResourc
es
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
fail
(
'Setting language and locales should not fail:
\n
$e
'
);
fail
(
'Setting language and locales should not fail:
\n
$e
'
);
}
}
...
@@ -468,7 +468,7 @@ void main() {
...
@@ -468,7 +468,7 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator
.
parseArbFil
es
();
generator
.
loadResourc
es
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
fail
(
'Setting language and locales should not fail:
\n
$e
'
);
fail
(
'Setting language and locales should not fail:
\n
$e
'
);
}
}
...
@@ -512,7 +512,7 @@ void main() {
...
@@ -512,7 +512,7 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator
.
parseArbFil
es
();
generator
.
loadResourc
es
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
fail
(
'Setting language and locales should not fail:
\n
$e
'
);
fail
(
'Setting language and locales should not fail:
\n
$e
'
);
}
}
...
@@ -538,7 +538,7 @@ void main() {
...
@@ -538,7 +538,7 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator
.
parseArbFil
es
();
generator
.
loadResourc
es
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
expect
(
e
.
message
,
contains
(
'locale could not be determined'
));
expect
(
e
.
message
,
contains
(
'locale could not be determined'
));
return
;
return
;
...
@@ -572,9 +572,9 @@ void main() {
...
@@ -572,9 +572,9 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator
.
parseArbFil
es
();
generator
.
loadResourc
es
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
expect
(
e
.
message
,
contains
(
'Multiple arb files with the same locale detected'
));
expect
(
e
.
message
,
contains
(
"Multiple arb files with the same 'en' locale detected"
));
return
;
return
;
}
}
...
@@ -585,11 +585,12 @@ void main() {
...
@@ -585,11 +585,12 @@ void main() {
});
});
});
});
group
(
'generateC
lassMethods
'
,
()
{
group
(
'generateC
ode
'
,
()
{
group
(
'DateTime tests'
,
()
{
group
(
'DateTime tests'
,
()
{
test
(
'throws an exception when improperly formatted date is passed in'
,
()
{
test
(
'throws an exception when improperly formatted date is passed in'
,
()
{
const
String
singleDateMessageArbFileString
=
'''
const
String
singleDateMessageArbFileString
=
'''
{
{
"@@locale": "en",
"springBegins": "Spring begins on {springStartDate}",
"springBegins": "Spring begins on {springStartDate}",
"@springBegins": {
"@springBegins": {
"description": "The first day of spring",
"description": "The first day of spring",
...
@@ -614,8 +615,8 @@ void main() {
...
@@ -614,8 +615,8 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator
.
parseArbFil
es
();
generator
.
loadResourc
es
();
generator
.
generateC
lassMethods
();
generator
.
generateC
ode
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
expect
(
e
.
message
,
contains
(
'asdf'
));
expect
(
e
.
message
,
contains
(
'asdf'
));
expect
(
e
.
message
,
contains
(
'springStartDate'
));
expect
(
e
.
message
,
contains
(
'springStartDate'
));
...
@@ -652,8 +653,8 @@ void main() {
...
@@ -652,8 +653,8 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator
.
parseArbFil
es
();
generator
.
loadResourc
es
();
generator
.
generateC
lassMethods
();
generator
.
generateC
ode
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
expect
(
e
.
message
,
contains
(
'the "format" attribute needs to be set'
));
expect
(
e
.
message
,
contains
(
'the "format" attribute needs to be set'
));
return
;
return
;
...
@@ -662,199 +663,6 @@ void main() {
...
@@ -662,199 +663,6 @@ void main() {
fail
(
'Improper date formatting should throw an exception'
);
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,
r'''
String springGreetings(DateTime springStartDate, Object helloWorld) {
final DateFormat springStartDateDateFormat = DateFormat.yMMMMEEEEd(_localeName);
final String springStartDateString = springStartDateDateFormat.format(springStartDate);
String springGreetings(Object springStartDate, Object helloWorld) {
return Intl.message(
"
Since
it
's
${springStartDate}
, it'
s
finally
spring
!
$
{
helloWorld
}!
",
locale: _localeName,
name: 'springGreetings',
desc: "
A
realization
that
it
's finally the spring season, followed by a greeting.",
args: <Object>[springStartDate, helloWorld]
);
}
return springGreetings(springStartDateString, helloWorld);
}
'''
);
});
});
group
(
'Number tests'
,
()
{
test
(
'correctly adds optional named parameters to numbers'
,
()
{
const
Set
<
String
>
numberFormatsWithNamedParameters
=
<
String
>{
'compact'
,
'compactCurrency'
,
'compactSimpleCurrency'
,
'compactLong'
,
'currency'
,
'decimalPercentPattern'
,
'simpleCurrency'
,
};
for
(
final
String
numberFormat
in
numberFormatsWithNamedParameters
)
{
final
String
singleNumberMessage
=
'''
{
"courseCompletion": "You have completed {progress} of the course.",
"@courseCompletion": {
"description": "The amount of progress the student has made in their class.",
"placeholders": {
"progress": {
"type": "double",
"format": "
$numberFormat
",
"optionalParameters": {
"decimalDigits": 2
}
}
}
}
}'''
;
final
Directory
l10nDirectory
=
fs
.
currentDirectory
.
childDirectory
(
'lib'
).
childDirectory
(
'l10n'
)
..
createSync
(
recursive:
true
);
l10nDirectory
.
childFile
(
defaultTemplateArbFileName
)
.
writeAsStringSync
(
singleNumberMessage
);
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 courseCompletion(double progress) {
final NumberFormat progressNumberFormat = NumberFormat.
$numberFormat
(
locale: _localeName,
decimalDigits: 2,
);
final String progressString = progressNumberFormat.format(progress);
String courseCompletion(Object progress) {
return Intl.message(
'
You
have
completed
\$
{
progress
}
of
the
course
.
',
locale: _localeName,
name: '
courseCompletion
',
desc: '
The
amount
of
progress
the
student
has
made
in
their
class
.',
args
:
<
Object
>[
progress
]
);
}
return
courseCompletion
(
progressString
);
}
''');}
});
test('
correctly
adds
optional
positional
parameters
to
numbers
', () {
const Set<String> numberFormatsWithPositionalParameters = <String>{
'
decimalPattern
',
'
percentPattern
',
'
scientificPattern
',
};
for (final String numberFormat in numberFormatsWithPositionalParameters) {
final String singleNumberMessage = '''
{
"courseCompletion"
:
"You have completed {progress} of the course."
,
"@courseCompletion"
:
{
"description"
:
"The amount of progress the student has made in their class."
,
"placeholders"
:
{
"progress"
:
{
"type"
:
"double"
,
"format"
:
"
$numberFormat
"
}
}
}
}
''';
final Directory l10nDirectory = fs.currentDirectory.childDirectory('
lib
').childDirectory('
l10n
')
..createSync(recursive: true);
l10nDirectory.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singleNumberMessage);
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
courseCompletion
(
double
progress
)
{
final
NumberFormat
progressNumberFormat
=
NumberFormat
.
$numberFormat
(
_localeName
);
final
String
progressString
=
progressNumberFormat
.
format
(
progress
);
String
courseCompletion
(
Object
progress
)
{
return
Intl
.
message
(
'You have completed
\
${progress}
of the course.'
,
locale:
_localeName
,
name:
'courseCompletion'
,
desc:
'The amount of progress the student has made in their class.'
,
args:
<
Object
>[
progress
]
);
}
return
courseCompletion
(
progressString
);
}
''');
}
});
test
(
'throws an exception when improperly formatted number is passed in'
,
()
{
test
(
'throws an exception when improperly formatted number is passed in'
,
()
{
const
String
singleDateMessageArbFileString
=
'''
const
String
singleDateMessageArbFileString
=
'''
{
{
...
@@ -882,8 +690,8 @@ void main() {
...
@@ -882,8 +690,8 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator.
parseArbFil
es();
generator
.
loadResourc
es
();
generator.generateC
lassMethods
();
generator
.
generateC
ode
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
expect
(
e
.
message
,
contains
(
'asdf'
));
expect
(
e
.
message
,
contains
(
'asdf'
));
expect
(
e
.
message
,
contains
(
'progress'
));
expect
(
e
.
message
,
contains
(
'progress'
));
...
@@ -918,8 +726,8 @@ void main() {
...
@@ -918,8 +726,8 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator.
parseArbFil
es();
generator
.
loadResourc
es
();
generator.generateC
lassMethods
();
generator
.
generateC
ode
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
expect
(
e
.
message
,
contains
(
'Check to see if the plural message is in the proper ICU syntax format'
));
expect
(
e
.
message
,
contains
(
'Check to see if the plural message is in the proper ICU syntax format'
));
return
;
return
;
...
@@ -950,8 +758,8 @@ void main() {
...
@@ -950,8 +758,8 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator.
parseArbFil
es();
generator
.
loadResourc
es
();
generator.generateC
lassMethods
();
generator
.
generateC
ode
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
expect
(
e
.
message
,
contains
(
'Check to see if the plural message is in the proper ICU syntax format'
));
expect
(
e
.
message
,
contains
(
'Check to see if the plural message is in the proper ICU syntax format'
));
return
;
return
;
...
@@ -978,8 +786,8 @@ void main() {
...
@@ -978,8 +786,8 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator.
parseArbFil
es();
generator
.
loadResourc
es
();
generator.generateC
lassMethods
();
generator
.
generateC
ode
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
expect
(
e
.
message
,
contains
(
'Resource attribute "@helloWorlds" was not found'
));
expect
(
e
.
message
,
contains
(
'Resource attribute "@helloWorlds" was not found'
));
return
;
return
;
...
@@ -1009,8 +817,8 @@ void main() {
...
@@ -1009,8 +817,8 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator.
parseArbFil
es();
generator
.
loadResourc
es
();
generator.generateC
lassMethods
();
generator
.
generateC
ode
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
expect
(
e
.
message
,
contains
(
'is not properly formatted'
));
expect
(
e
.
message
,
contains
(
'is not properly formatted'
));
expect
(
e
.
message
,
contains
(
'Ensure that it is a map with string valued keys'
));
expect
(
e
.
message
,
contains
(
'Ensure that it is a map with string valued keys'
));
...
@@ -1041,8 +849,8 @@ void main() {
...
@@ -1041,8 +849,8 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator.
parseArbFil
es();
generator
.
loadResourc
es
();
generator.generateC
lassMethods
();
generator
.
generateC
ode
();
}
on
FormatException
catch
(
e
)
{
}
on
FormatException
catch
(
e
)
{
expect
(
e
.
message
,
contains
(
'Unexpected character'
));
expect
(
e
.
message
,
contains
(
'Unexpected character'
));
return
;
return
;
...
@@ -1072,8 +880,8 @@ void main() {
...
@@ -1072,8 +880,8 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator.
parseArbFil
es();
generator
.
loadResourc
es
();
generator.generateC
lassMethods
();
generator
.
generateC
ode
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
expect
(
e
.
message
,
contains
(
'Resource attribute "@title" was not found'
));
expect
(
e
.
message
,
contains
(
'Resource attribute "@title" was not found'
));
return
;
return
;
...
@@ -1089,11 +897,11 @@ void main() {
...
@@ -1089,11 +897,11 @@ void main() {
test
(
'cannot contain non-alphanumeric symbols'
,
()
{
test
(
'cannot contain non-alphanumeric symbols'
,
()
{
const
String
nonAlphaNumericArbFile
=
'''
const
String
nonAlphaNumericArbFile
=
'''
{
{
"title!!"
:
"Stocks"
,
"title!!": "Stocks",
"@title!!"
:
{
"@title!!": {
"description"
:
"Title for the Stocks application"
"description": "Title for the Stocks application"
}
}
}
''';
}'''
;
final
Directory
l10nDirectory
=
fs
.
currentDirectory
.
childDirectory
(
'lib'
).
childDirectory
(
'l10n'
)
final
Directory
l10nDirectory
=
fs
.
currentDirectory
.
childDirectory
(
'lib'
).
childDirectory
(
'l10n'
)
..
createSync
(
recursive:
true
);
..
createSync
(
recursive:
true
);
l10nDirectory
.
childFile
(
defaultTemplateArbFileName
)
l10nDirectory
.
childFile
(
defaultTemplateArbFileName
)
...
@@ -1107,10 +915,10 @@ void main() {
...
@@ -1107,10 +915,10 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator.
parseArbFil
es();
generator
.
loadResourc
es
();
generator.generateC
lassMethods
();
generator
.
generateC
ode
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
expect(e.message, contains('
Invalid
key
format
'));
expect
(
e
.
message
,
contains
(
'Invalid
ARB resource name
'
));
return
;
return
;
}
}
...
@@ -1120,11 +928,11 @@ void main() {
...
@@ -1120,11 +928,11 @@ void main() {
test
(
'must start with lowercase character'
,
()
{
test
(
'must start with lowercase character'
,
()
{
const
String
nonAlphaNumericArbFile
=
'''
const
String
nonAlphaNumericArbFile
=
'''
{
{
"Title"
:
"Stocks"
,
"Title": "Stocks",
"@Title"
:
{
"@Title": {
"description"
:
"Title for the Stocks application"
"description": "Title for the Stocks application"
}
}
}
''';
}'''
;
final
Directory
l10nDirectory
=
fs
.
currentDirectory
.
childDirectory
(
'lib'
).
childDirectory
(
'l10n'
)
final
Directory
l10nDirectory
=
fs
.
currentDirectory
.
childDirectory
(
'lib'
).
childDirectory
(
'l10n'
)
..
createSync
(
recursive:
true
);
..
createSync
(
recursive:
true
);
l10nDirectory
.
childFile
(
defaultTemplateArbFileName
)
l10nDirectory
.
childFile
(
defaultTemplateArbFileName
)
...
@@ -1138,10 +946,10 @@ void main() {
...
@@ -1138,10 +946,10 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator.
parseArbFil
es();
generator
.
loadResourc
es
();
generator.generateC
lassMethods
();
generator
.
generateC
ode
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
expect(e.message, contains('
Invalid
key
format
'));
expect
(
e
.
message
,
contains
(
'Invalid
ARB resource name
'
));
return
;
return
;
}
}
...
@@ -1151,11 +959,11 @@ void main() {
...
@@ -1151,11 +959,11 @@ void main() {
test
(
'cannot start with a number'
,
()
{
test
(
'cannot start with a number'
,
()
{
const
String
nonAlphaNumericArbFile
=
'''
const
String
nonAlphaNumericArbFile
=
'''
{
{
"123title"
:
"Stocks"
,
"123title": "Stocks",
"@123title"
:
{
"@123title": {
"description"
:
"Title for the Stocks application"
"description": "Title for the Stocks application"
}
}
}
''';
}'''
;
final
Directory
l10nDirectory
=
fs
.
currentDirectory
.
childDirectory
(
'lib'
).
childDirectory
(
'l10n'
)
final
Directory
l10nDirectory
=
fs
.
currentDirectory
.
childDirectory
(
'lib'
).
childDirectory
(
'l10n'
)
..
createSync
(
recursive:
true
);
..
createSync
(
recursive:
true
);
l10nDirectory
.
childFile
(
defaultTemplateArbFileName
)
l10nDirectory
.
childFile
(
defaultTemplateArbFileName
)
...
@@ -1169,10 +977,10 @@ void main() {
...
@@ -1169,10 +977,10 @@ void main() {
outputFileString:
defaultOutputFileString
,
outputFileString:
defaultOutputFileString
,
classNameString:
defaultClassNameString
,
classNameString:
defaultClassNameString
,
);
);
generator.
parseArbFil
es();
generator
.
loadResourc
es
();
generator.generateC
lassMethods
();
generator
.
generateC
ode
();
}
on
L10nException
catch
(
e
)
{
}
on
L10nException
catch
(
e
)
{
expect(e.message, contains('
Invalid
key
format
'));
expect
(
e
.
message
,
contains
(
'Invalid
ARB resource name
'
));
return
;
return
;
}
}
...
...
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