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

Initial implementation #1

Merged
merged 4 commits into from
Oct 12, 2019
Merged
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
3 changes: 3 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
analyzer:
# enable-experiment:
# - non-nullable
2 changes: 1 addition & 1 deletion example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ android {

defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "bratan.me.example"
applicationId "leadcode.io.example"
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
Expand Down
2 changes: 1 addition & 1 deletion example/android/app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="bratan.me.example">
package="leadcode.io.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
Expand Down
2 changes: 1 addition & 1 deletion example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="bratan.me.example">
package="leadcode.io.example">

<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package bratan.me.example;
package leadcode.io.example;

import android.os.Bundle;
import io.flutter.app.FlutterActivity;
Expand Down
2 changes: 1 addition & 1 deletion example/android/app/src/profile/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="bratan.me.example">
package="leadcode.io.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
Expand Down
6 changes: 3 additions & 3 deletions example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = bratan.me.example;
PRODUCT_BUNDLE_IDENTIFIER = leadcode.io.example;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
Expand Down Expand Up @@ -453,7 +453,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = bratan.me.example;
PRODUCT_BUNDLE_IDENTIFIER = leadcode.io.example;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
Expand All @@ -476,7 +476,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = bratan.me.example;
PRODUCT_BUNDLE_IDENTIFIER = leadcode.io.example;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
Expand Down
4 changes: 3 additions & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: flutter_translate_example
name: example
description: Example project for the flutter_translate library

# The following defines the version and build number for your application.
Expand Down Expand Up @@ -29,3 +29,5 @@ dev_dependencies:
flutter:

uses-material-design: true
assets:
- assets/i18n/
5 changes: 5 additions & 0 deletions lib/flutter_translate.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
library flutter_translate;

export 'package:flutter_translate/localization.dart';
export 'package:flutter_translate/localization_configuration.dart';
export 'package:flutter_translate/localization_delegate.dart';
export 'package:flutter_translate/localization_provider.dart';
export 'package:flutter_translate/localized_app.dart';
export 'package:flutter_translate/localized_app_state.dart';
17 changes: 17 additions & 0 deletions lib/helpers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'dart:ui';

Locale localeFromString(String code, {bool languageCodeOnly})
{
if(code.contains('_'))
{
var parts = code.split('_');

return languageCodeOnly ? Locale(parts[0]) : Locale(parts[0], parts[1]);
}
else
{
return Locale(code);
}
}


41 changes: 39 additions & 2 deletions lib/localization.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,45 @@
import 'package:flutter/widgets.dart';
import 'package:flutter/cupertino.dart';

class Localization
{
Locale locale;
final Map<String, dynamic> translations;

Localization(this.translations);

static Localization of(BuildContext context) => Localizations.of<Localization>(context, Localization);

String key(String key, {List<String> args})
{
var value = _getValue(key, translations);

if (args != null)
{
args.forEach((String str)
{
value = value.replaceFirst(RegExp(r'{}'), str);
});
}

return value;
}

String _getValue(String path, Map<String, dynamic> map)
{
List<String> keys = path.split('.');

if (keys.length > 1)
{
for (int index = 0; index <= keys.length; index++)
{
if (map.containsKey(keys[index]) && map[keys[index]] is! String)
{
return _getValue(keys.sublist(index + 1, keys.length).join('.'), map[keys[index]]);
}

return map[path] ?? path;
}
}

return map[path] ?? path;
}
}
60 changes: 60 additions & 0 deletions lib/localization_configuration.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import 'package:flutter/widgets.dart';
import 'helpers.dart';
import 'localization_file_service.dart';

class LocalizationConfiguration
{
Map<Locale, String> _localizations;

Map<Locale, String> get localizations => _localizations;

final Locale fallbackLocale;

final List<Locale> supportedLocales;

LocalizationConfiguration._(this.fallbackLocale, this.supportedLocales);

static Future<LocalizationConfiguration> create({@required String fallbackLanguage, @required List<String> supportedLanguages, @required String basePath}) async
{
var configuration = new LocalizationConfiguration._(localeFromString(fallbackLanguage), _generateSupportedLocales(supportedLanguages));

_validateConfiguration(fallbackLanguage, supportedLanguages);

var files = await LocalizationFileService.getLocalizedFiles(supportedLanguages, basePath);

configuration._localizations = files.map((x,y) => _getLocalizedEntry(x, y));

return configuration;
}

static void _validateConfiguration(String fallbackLanguage, List<String> supportedLanguages)
{
if(!supportedLanguages.contains(fallbackLanguage))
{
throw new Exception('The fallbackLanguage [$fallbackLanguage] must be present in the supportedLanguages list [${supportedLanguages.join(", ")}].');
}
}

static List<Locale> _generateSupportedLocales(List<String> supportedLanguages)
{
return supportedLanguages.map((x) => localeFromString(x, languageCodeOnly: true)).toSet().toList();
}

static MapEntry<Locale, String> _getLocalizedEntry(String languageCode, String file)
{
Locale locale;

if(languageCode.contains('_'))
{
var parts = languageCode.split('_');

locale = new Locale(parts[0], parts[1]);
}
else
{
locale = new Locale(languageCode);
}

return MapEntry(locale, file);
}
}
50 changes: 50 additions & 0 deletions lib/localization_delegate.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'localization_file_service.dart';
import 'localization_configuration.dart';
import 'localization.dart';

class LocalizationDelegate extends LocalizationsDelegate<Localization>
{
final LocalizationConfiguration configuration;

LocalizationDelegate(this.configuration);

@override
Future<Localization> load(Locale newLocale) async
{
var locale = _findLocale(newLocale) ?? configuration.fallbackLocale;

var localizedContent = await _getLocalizedContent(locale);

return new Localization(localizedContent);
}

Future<Map<String, dynamic>> _getLocalizedContent(Locale locale) async
{
var file = configuration.localizations[locale];

var content = await LocalizationFileService.getLocalizedContent(file);

return json.decode(content);
}


Locale _findLocale(Locale locale)
{
var existing = configuration.localizations.keys.firstWhere((x) => x == locale, orElse: () => null);

if(existing == null)
{
existing = configuration.localizations.keys.firstWhere((x) => x.languageCode == locale.languageCode, orElse: () => null);
}

return existing;
}

@override
bool isSupported(Locale locale) => locale != null;

@override
bool shouldReload(LocalizationsDelegate<Localization> old) => true;
}
62 changes: 62 additions & 0 deletions lib/localization_file_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'dart:convert';
import 'package:flutter/services.dart';

class LocalizationFileService
{
static const String assetManifestFilename = 'AssetManifest.json';

static Future<Map<String, String>> getLocalizedFiles(List<String> languages, String basePath) async
{
var localizedFiles = await _getAllLocalizedFiles(basePath);

final files = new Map<String, String>();

for(final language in languages.toSet())
{
var file = _findLocalizationFile(language, localizedFiles, basePath);

files[language] = file;
}

return files;
}

static Future<String> getLocalizedContent(String file) async
{
return await rootBundle.loadString(file);
}

static Future<List<String>> _getAllLocalizedFiles(String basePath) async
{
final manifest = await rootBundle.loadString(assetManifestFilename);

Map<String, dynamic> map = jsonDecode(manifest);

return map.keys.where((x) => x.startsWith('$basePath/')).toList();
}

static String _findLocalizationFile(String languageCode, List<String> localizedFiles, String basePath)
{
var file = _getFilepath(languageCode, basePath);

if(!localizedFiles.contains(file))
{
if(languageCode.contains('_'))
{
file = _getFilepath(languageCode.split('_').first, basePath);
}
}

if(file == null)
{
throw new Exception('The asset file for the language "$languageCode" was not found.');
}

return file;
}

static String _getFilepath(String languageCode, String basePath)
{
return '$basePath/$languageCode.json';
}
}
16 changes: 16 additions & 0 deletions lib/localization_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:flutter/widgets.dart';
import 'localized_app_state.dart';

class LocalizationProvider extends InheritedWidget
{
final LocalizedAppState state;

final Widget child;

LocalizationProvider({Key key, this.child, this.state}) : super(key: key, child: child);

static LocalizationProvider of(BuildContext context) => (context.inheritFromWidgetOfExactType(LocalizationProvider) as LocalizationProvider);

@override
bool updateShouldNotify(LocalizationProvider oldWidget) => true;
}
11 changes: 11 additions & 0 deletions lib/localized_app.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:flutter/widgets.dart';
import 'localized_app_state.dart';

class LocalizedApp extends StatefulWidget
{
final Widget child;

LocalizedApp(this.child);

LocalizedAppState createState() => LocalizedAppState();
}
18 changes: 18 additions & 0 deletions lib/localized_app_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:flutter/widgets.dart';
import 'localized_app.dart';
import 'localization_provider.dart';

class LocalizedAppState extends State<LocalizedApp>
{
Locale _currentLocale;

Locale get currentLocale => _currentLocale;

void changeLanguage(Locale newLocale)
{
setState(() => _currentLocale = newLocale);
}

@override
Widget build(BuildContext context) => LocalizationProvider(state: this, child: widget.child);
}
4 changes: 3 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_translate
description: The internationalization (i18n) library for Flutter.
version: 0.0.2
version: 1.0.0
author: Leadcode <dev@leadcode.io>
homepage: https://github.com/leadcode/flutter_translate

Expand All @@ -10,6 +10,8 @@ environment:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter

dev_dependencies:

Expand Down