Unverified Commit 0963d725 authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

Make resource attributes optional for simple cases (#68774)

parent 6688e63f
......@@ -61,6 +61,7 @@ void generateLocalizations({
headerFile: options?.headerFile?.toFilePath(),
useDeferredLoading: options.deferredLoading ?? false,
useSyntheticPackage: options.useSyntheticPackage ?? true,
areResourceAttributesRequired: options.areResourceAttributesRequired ?? false,
)
..loadResources()
..writeOutputFiles()
......@@ -161,6 +162,7 @@ class LocalizationOptions {
this.headerFile,
this.deferredLoading,
this.useSyntheticPackage = true,
this.areResourceAttributesRequired = false,
}) : assert(useSyntheticPackage != null);
/// The `--arb-dir` argument.
......@@ -211,6 +213,12 @@ class LocalizationOptions {
/// Whether to generate the Dart localization files in a synthetic package
/// or in a custom directory.
final bool useSyntheticPackage;
/// The `required-resource-attributes` argument.
///
/// Whether to require all resource ids to contain a corresponding
/// resource attribute.
final bool areResourceAttributesRequired;
}
/// Parse the localizations configuration options from [file].
......@@ -243,6 +251,7 @@ LocalizationOptions parseLocalizationsOptions({
headerFile: _tryReadUri(yamlMap, 'header-file', logger),
deferredLoading: _tryReadBool(yamlMap, 'use-deferred-loading', logger),
useSyntheticPackage: _tryReadBool(yamlMap, 'synthetic-package', logger) ?? true,
areResourceAttributesRequired: _tryReadBool(yamlMap, 'required-resource-attributes', logger) ?? false,
);
}
......
......@@ -150,6 +150,14 @@ class GenerateLocalizationsCommand extends FlutterCommand {
'\n\n'
'When null, the relative path to the present working directory will be used.'
);
argParser.addFlag(
'required-resource-attributes',
help: 'Requires all resource ids to contain a corresponding resource attribute.\n\n'
'By default, simple messages will not require metadata, but it is highly '
'recommended as this provides context for the meaning of a message to '
'readers.\n\n'
'Resource attributes are still required for plural messages.'
);
}
final FileSystem _fileSystem;
......@@ -178,6 +186,7 @@ class GenerateLocalizationsCommand extends FlutterCommand {
final String inputsAndOutputsListPath = stringArg('gen-inputs-and-outputs-list');
final bool useSyntheticPackage = boolArg('synthetic-package');
final String projectPathString = stringArg('project-dir');
final bool areResourceAttributesRequired = boolArg('required-resource-attributes');
final LocalizationsGenerator localizationsGenerator = LocalizationsGenerator(_fileSystem);
......@@ -196,6 +205,7 @@ class GenerateLocalizationsCommand extends FlutterCommand {
inputsAndOutputsListPath: inputsAndOutputsListPath,
useSyntheticPackage: useSyntheticPackage,
projectPathString: projectPathString,
areResourceAttributesRequired: areResourceAttributesRequired,
)
..loadResources()
..writeOutputFiles()
......
......@@ -526,6 +526,12 @@ class LocalizationsGenerator {
List<String> _inputFileList;
List<String> _outputFileList;
/// Whether or not resource attributes are required for each corresponding
/// resource id.
///
/// Resource attributes provide metadata about the message.
bool _areResourceAttributesRequired;
/// Initializes [inputDirectory], [outputDirectory], [templateArbFile],
/// [outputFile] and [className].
///
......@@ -547,6 +553,7 @@ class LocalizationsGenerator {
String inputsAndOutputsListPath,
bool useSyntheticPackage = true,
String projectPathString,
bool areResourceAttributesRequired = false,
}) {
_useSyntheticPackage = useSyntheticPackage;
setProjectDir(projectPathString);
......@@ -559,6 +566,7 @@ class LocalizationsGenerator {
_setUseDeferredLoading(useDeferredLoading);
className = classNameString;
_setInputsAndOutputsListFile(inputsAndOutputsListPath);
_areResourceAttributesRequired = areResourceAttributesRequired;
}
static bool _isNotReadable(FileStat fileStat) {
......@@ -793,7 +801,9 @@ class LocalizationsGenerator {
void loadResources() {
final AppResourceBundle templateBundle = AppResourceBundle(templateArbFile);
_templateArbLocale = templateBundle.locale;
_allMessages = templateBundle.resourceIds.map((String id) => Message(templateBundle.resources, id));
_allMessages = templateBundle.resourceIds.map((String id) => Message(
templateBundle.resources, id, _areResourceAttributesRequired,
));
for (final String resourceId in templateBundle.resourceIds) {
if (!_isValidGetterAndMethodName(resourceId)) {
throw L10nException(
......
......@@ -270,12 +270,12 @@ class Placeholder {
// 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 {
Message(Map<String, dynamic> bundle, this.resourceId)
Message(Map<String, dynamic> bundle, this.resourceId, bool isResourceAttributeRequired)
: assert(bundle != null),
assert(resourceId != null && resourceId.isNotEmpty),
value = _value(bundle, resourceId),
description = _description(bundle, resourceId),
placeholders = _placeholders(bundle, resourceId),
description = _description(bundle, resourceId, isResourceAttributeRequired),
placeholders = _placeholders(bundle, resourceId, isResourceAttributeRequired),
_pluralMatch = _pluralRE.firstMatch(_value(bundle, resourceId));
static final RegExp _pluralRE = RegExp(r'\s*\{([\w\s,]*),\s*plural\s*,');
......@@ -312,25 +312,51 @@ class Message {
return bundle[resourceId] as String;
}
static Map<String, dynamic> _attributes(Map<String, dynamic> bundle, String resourceId) {
static Map<String, dynamic> _attributes(
Map<String, dynamic> bundle,
String resourceId,
bool isResourceAttributeRequired,
) {
final dynamic attributes = bundle['@$resourceId'];
if (attributes == null) {
throw L10nException(
'Resource attribute "@$resourceId" was not found. Please '
'ensure that each resource has a corresponding @resource.'
);
if (isResourceAttributeRequired) {
if (attributes == null) {
throw L10nException(
'Resource attribute "@$resourceId" was not found. Please '
'ensure that each resource has a corresponding @resource.'
);
}
}
if (attributes is! Map<String, dynamic>) {
if (attributes != null && attributes is! Map<String, dynamic>) {
throw L10nException(
'The resource attribute "@$resourceId" is not a properly formatted Map. '
'Ensure that it is a map with keys that are strings.'
);
}
final RegExpMatch pluralRegExp = _pluralRE.firstMatch(_value(bundle, resourceId));
final bool isPlural = pluralRegExp != null && pluralRegExp.groupCount == 1;
if (attributes == null && isPlural) {
throw L10nException(
'Resource attribute "@$resourceId" was not found. Please '
'ensure that plural resources have a corresponding @resource.'
);
}
return attributes as Map<String, dynamic>;
}
static String _description(Map<String, dynamic> bundle, String resourceId) {
final dynamic value = _attributes(bundle, resourceId)['description'];
static String _description(
Map<String, dynamic> bundle,
String resourceId,
bool isResourceAttributeRequired,
) {
final Map<String, dynamic> resourceAttributes = _attributes(bundle, resourceId, isResourceAttributeRequired);
if (resourceAttributes == null) {
return null;
}
final dynamic value = resourceAttributes['description'];
if (value == null) {
return null;
}
......@@ -342,8 +368,16 @@ class Message {
return value as String;
}
static List<Placeholder> _placeholders(Map<String, dynamic> bundle, String resourceId) {
final dynamic value = _attributes(bundle, resourceId)['placeholders'];
static List<Placeholder> _placeholders(
Map<String, dynamic> bundle,
String resourceId,
bool isResourceAttributeRequired,
) {
final Map<String, dynamic> resourceAttributes = _attributes(bundle, resourceId, isResourceAttributeRequired);
if (resourceAttributes == null) {
return <Placeholder>[];
}
final dynamic value = resourceAttributes['placeholders'];
if (value == null) {
return <Placeholder>[];
}
......
......@@ -39,6 +39,7 @@ void main() {
templateArbFile: Uri.file('example.arb'),
untranslatedMessagesFile: Uri.file('untranslated'),
useSyntheticPackage: false,
areResourceAttributesRequired: true,
);
final LocalizationsGenerator mockLocalizationsGenerator = MockLocalizationsGenerator();
......@@ -52,18 +53,19 @@ void main() {
verify(
mockLocalizationsGenerator.initialize(
inputPathString: 'arb',
outputPathString: null,
templateArbFileName: 'example.arb',
outputFileString: 'bar',
classNameString: 'Foo',
preferredSupportedLocale: <String>['en_US'],
headerString: 'HEADER',
headerFile: 'header',
useDeferredLoading: true,
inputsAndOutputsListPath: '/',
useSyntheticPackage: false,
projectPathString: '/',
inputPathString: 'arb',
outputPathString: null,
templateArbFileName: 'example.arb',
outputFileString: 'bar',
classNameString: 'Foo',
preferredSupportedLocale: <String>['en_US'],
headerString: 'HEADER',
headerFile: 'header',
useDeferredLoading: true,
inputsAndOutputsListPath: '/',
useSyntheticPackage: false,
projectPathString: '/',
areResourceAttributesRequired: true,
),
).called(1);
verify(mockLocalizationsGenerator.loadResources()).called(1);
......
......@@ -1587,7 +1587,7 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e
},
);
test('should throw when resource is missing resource attribute', () {
test('should throw when resource is missing resource attribute (isResourceAttributeRequired = true)', () {
const String arbFileWithMissingResourceAttribute = '''
{
"title": "Stocks"
......@@ -1605,6 +1605,7 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
areResourceAttributesRequired: true,
);
generator.loadResources();
generator.writeOutputFiles();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment