Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrated to null safety #8

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![pub package][pub-package-badge]][pub-package]
[![Flutter workflow][flutter-workflow-badge]][flutter-workflow]

A package providing support for internationalizing Flutter applications using [intl] package with [Arbify].
A wrapper of [intl_utils](https://pub.dev/packages/intl_utils). Provides your translations server instead of `localizely` server

## Usage

Expand All @@ -23,9 +23,20 @@ Use `flutter pub run arbify:download` to run a command-line utility that will gu

```yaml
arbify:
url: https://arb.company.com
project_id: 17
outpur_dir: lib/l10n # default, can be ommited
url: https://arb.company.com
project_id: 17
outpur_dir: lib/l10n # default, can be omitted
```

Additional configs from [intl_utils](https://pub.dev/packages/intl_utils):
```yaml
flutter_intl:
enabled: false # Required. If true IDE plugin will watch changes of files and generate it by itself
class_name: S # Optional. Sets the name for the generated localization class. Default: S
main_locale: en # Optional. Sets the main locale used for generating localization files. Provided value should consist of language code and optional script and country codes separated with underscore (e.g. 'en', 'en_GB', 'zh_Hans', 'zh_Hans_CN'). Default: en
arb_dir: lib/l10n # Optional. Sets the directory of your ARB resource files. Provided value should be a valid path on your system. Default: lib/l10n
output_dir: lib/generated # Optional. Sets the directory of generated localization files. Provided value should be a valid path on your system. Default: lib/generated
use_deferred_loading: false # Optional. Must be set to true to generate localization code that is loaded with deferred loading. Default: false
```

2. Adding your secret (obtained at https://arb.company.com/account/secrets/create) to `.secret.arbify` file.
Expand Down
33 changes: 32 additions & 1 deletion bin/download.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
import 'package:arbify/arbify_download.dart';
import 'package:args/args.dart';
import 'package:universal_io/io.dart';

Future<void> main(List<String> arguments) async {
final _argParser = ArgParser()
..addFlag(
'help',
abbr: 'h',
negatable: false,
help: 'Shows this help message.',
)
..addFlag(
'interactive',
abbr: 'i',
defaultsTo: true,
help: 'Whether the command-line utility can ask you interactively.',
)
..addOption(
'secret',
abbr: 's',
valueHelp: 'secret',
help: 'Secret to be used for authenticating to the Arbify API.\n'
'Overrides the secret from the .secret.arbify file.',
);

final ArgResults args = _argParser.parse(arguments);

if (args['help'] as bool) {
print('Arbify download command-line utility.\n');
print(_argParser.usage);
exit(0);
}

Future<void> main(List<String> args) async {
await ArbifyCli().run(args);
}
7 changes: 4 additions & 3 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@ publish_to: 'none'
version: 1.0.0+1

environment:
sdk: ">=2.7.0 <3.0.0"
sdk: ">=2.12.0 <3.0.0"

dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.16.1
intl: ^0.17.0

dev_dependencies:
arbify:
path: ../
flutter_test:
sdk: flutter
arbify: ^0.0.6

flutter:
uses-material-design: true
Expand Down
3 changes: 0 additions & 3 deletions lib/arbify_download.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ export 'src/api/arbify_api.dart';
export 'src/arb_parser/arb_file.dart';
export 'src/arb_parser/arb_parser.dart';
export 'src/arbify_cli.dart';
export 'src/config/config.dart';
export 'src/config/pubspec_config.dart';
export 'src/config/secret.dart';
export 'src/generator/intl_translation.dart';
export 'src/generator/l10n_dart_generator.dart';
export 'src/output_file_utils.dart';
51 changes: 26 additions & 25 deletions lib/src/api/arbify_api.dart
Original file line number Diff line number Diff line change
@@ -1,40 +1,41 @@
import 'package:meta/meta.dart';
import 'package:dio/dio.dart';

import 'export_info.dart';

class ArbifyApi {
static const _apiPrefix = '/api/v1';
late final Dio _client;

final Dio _client;
ArbifyApi({required Uri apiUrl, required String secret, Dio? client}) {
_client = client ?? Dio();

ArbifyApi({@required Uri apiUrl, @required String secret, Dio client})
: _client = client ?? Dio() {
_client.options = _client.options.merge(
baseUrl: apiUrl.toString() + _apiPrefix,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json; charset=utf-8',
'Authorization': 'Bearer $secret',
},
);
final options = _client.options;
options.baseUrl = '$apiUrl/api/v1';
options.headers = {
'Accept': 'application/json',
'Content-Type': 'application/json; charset=utf-8',
'Authorization': 'Bearer $secret',
};
}

/// Fetches available exports with their last modification date from
/// a project with a given [projectId].
Future<List<ExportInfo>> fetchAvailableExports(int projectId) async {
return _client.get('/projects/$projectId/arb').then((response) {
return (response.data as Map<String, dynamic>)
.entries
.map((entry) =>
ExportInfo(entry.key, DateTime.parse(entry.value as String)))
.toList();
});
Future<List<ExportInfo>> fetchAvailableExportsForProj(int projectId) async {
final response = await _client.get('/projects/$projectId/arb');

return (response.data as Map<String, dynamic>).entries.map((entry) {
return ExportInfo(
languageCode: entry.key,
lastModified: DateTime.parse(entry.value as String),
);
}).toList();
}

Future<String> fetchExport(int projectId, String languageCode) async {
return _client
.get('/projects/$projectId/arb/$languageCode')
.then((response) => response.data as String);
Future<String> fetchExport({
required int projectId,
required String languageCode,
}) async {
final path = '/projects/$projectId/arb/$languageCode';
final response = await _client.get(path);
return response.data as String;
}
}
2 changes: 1 addition & 1 deletion lib/src/api/export_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ class ExportInfo {
final String languageCode;
final DateTime lastModified;

ExportInfo(this.languageCode, this.lastModified);
ExportInfo({required this.languageCode, required this.lastModified});

@override
String toString() =>
Expand Down
8 changes: 4 additions & 4 deletions lib/src/arb_parser/arb_file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ import 'arb_message.dart';
class ArbFile {
/// [locale] is the locale for which messages/resources are stored
/// in this file.
final String locale;
final String? locale;

/// [context] describes (in text) the context in which all these
/// resources apply.
final String context;
final String? context;

/// [lastModified] is the last modified time of this ARB file/data.
final DateTime lastModified;
final DateTime? lastModified;

/// [author] is the author of these messages. In the case of localized
/// ARB files it can contain the names/details of the translator.
final String author;
final String? author;

/// [customAttributes] is a map of customized attributes that are
/// the attributes prefixed with "x-".
Expand Down
22 changes: 10 additions & 12 deletions lib/src/arb_parser/arb_message.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import 'package:meta/meta.dart';

// https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification
class ArbMessage {
/// [id] is the resource id is the identifier for the resource in a given
Expand All @@ -23,7 +21,7 @@ class ArbMessage {
/// [type] describes the type of resource. Possible values are "text",
/// "image", "css". Program should not rely on this attribute in run time.
/// It is mainly for the localization tools.
final String type;
final String? type;

/// [context] describes (in text) the context in which this resource applies.
/// Context is organized in hierarchy, and level separated by ":".
Expand All @@ -34,12 +32,12 @@ class ArbMessage {
/// Example:
///
/// "context":"homePage:Print dialog box"
final String context;
final String? context;

/// [description] is a short paragraph describing the resource and how it is
/// being used by the app, and message that need to be passed to
/// localization process and translators.
final String description;
final String? description;

/// [placeholders] is a map from placeholder id to placeholder properties,
/// including description and example. Placeholder can be specified using
Expand Down Expand Up @@ -100,23 +98,23 @@ class ArbMessage {
/// }
/// }
/// },
final Map<String, Map<String, String>> placeholders;
final Map<String, Map<String, String>>? placeholders;

/// [screenshot] is a URL to the image location or base-64 encoded image
/// data.
final String screenshot;
final String? screenshot;

/// [video] is a URL to a video of the app/resource/widget in action.
final String video;
final String? video;

/// [sourceText] is the source of the text from where this message is
/// translated from. This is used to track source arb change and determine
/// if this message need to be updated.
final String sourceText;
final String? sourceText;

/// [customAttributes] is a map of customized attributes that are
/// the attributes prefixed with "x-".
final Map<String, dynamic> customAttributes;
final Map<String, dynamic>? customAttributes;

/// Resource values ([value]) in an ARB file is always in the form of
/// a string. Most of those strings represent translatable text. Some strings
Expand All @@ -127,7 +125,8 @@ class ArbMessage {
final String value;

ArbMessage({
@required this.id,
required this.id,
required this.value,
this.type,
this.context,
this.description,
Expand All @@ -136,6 +135,5 @@ class ArbMessage {
this.video,
this.sourceText,
this.customAttributes,
@required this.value,
});
}
40 changes: 21 additions & 19 deletions lib/src/arb_parser/arb_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'arb_file.dart';
import 'arb_message.dart';

class ArbParser {
ArbFile parseString(String content) {
ArbFile parseArbFile(String content) {
final json = jsonDecode(content) as Map<String, dynamic>;

final messages = <ArbMessage>[];
Expand All @@ -13,18 +13,18 @@ class ArbParser {
return;
}

final attributes = json['@$key'] as Map<String, dynamic>;
final attributes = json['@$key'] as Map<String, dynamic>?;
final message = parseMessage(key, value as String, attributes);

messages.add(message);
});

final file = ArbFile(
messages: messages,
locale: json['@@locale'] as String,
context: json['@@context'] as String,
lastModified: DateTime.tryParse(json['@@last_modified'] as String),
author: json['@@author'] as String,
locale: json['@@locale'] as String?,
context: json['@@context'] as String?,
lastModified: DateTime.tryParse(json['@@last_modified'] as String? ?? ''),
author: json['@@author'] as String?,
);

return file;
Expand All @@ -33,34 +33,36 @@ class ArbParser {
ArbMessage parseMessage(
String id,
String value,
Map<String, dynamic> attributes,
Map<String, dynamic>? attributes,
) {
final attrs = attributes ?? {};

final customAttributes = parseCustomAttributes(attrs);
final message = ArbMessage(
id: id,
value: value,
type: attrs['type'] as String,
context: attrs['context'] as String,
description: attrs['description'] as String,
placeholders: attrs['placeholders'] as Map<String, Map<String, String>>,
screenshot: attrs['screenshot'] as String,
video: attrs['video'] as String,
sourceText: attrs['source_text'] as String,
type: attrs['type'] as String?,
context: attrs['context'] as String?,
description: attrs['description'] as String?,
placeholders: attrs['placeholders'] as Map<String, Map<String, String>>?,
screenshot: attrs['screenshot'] as String?,
video: attrs['video'] as String?,
sourceText: attrs['source_text'] as String?,
customAttributes: customAttributes,
);

return message;
}

Map<String, dynamic> parseCustomAttributes(Map<String, dynamic> attributes) {
Map<String, dynamic>? parseCustomAttributes(Map<String, dynamic> attributes) {
final entries = attributes.entries
.where((attribute) => attribute.key.startsWith('x-'))
.map((attribute) => MapEntry(
attribute.key.substring(2),
attribute.value,
));
.map((attribute) {
return MapEntry(
attribute.key.substring(2),
attribute.value,
);
});

return Map.fromEntries(entries);
}
Expand Down
Loading