Skip to content

Commit

Permalink
feat: implement slang_gpt
Browse files Browse the repository at this point in the history
  • Loading branch information
Tienisto committed Jul 25, 2023
1 parent 7c4c5b1 commit e7e35f1
Show file tree
Hide file tree
Showing 45 changed files with 1,415 additions and 133 deletions.
25 changes: 25 additions & 0 deletions slang/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ dart run slang migrate arb src.arb dest.json # migrate arb to json
- [Apply Translations](#-apply-translations)
- [Edit Translations](#-edit-translations)
- [Outdated Translations](#-outdated-translations)
- [Translate with GPT](#-translate-with-gpt)
- [Migration](#-migration)
- [ARB](#arb)
- [Statistics](#-statistics)
Expand Down Expand Up @@ -1437,6 +1438,30 @@ This will add an `(OUTDATED)` modifier to all secondary locales.

You can also add these flags manually!

### ➤ Translate with GPT

Take advantage of GPT to internationalize your app with context-aware translations.

Import [slang_gpt](https://pub.dev/packages/slang_gpt) to your `dev_dependencies`.

Then add the following configuration:

```yaml
# existing config
base_locale: fr
fallback_strategy: base_locale
input_directory: lib/i18n
input_file_pattern: .i18n.json
output_directory: lib/i18n
# add this
gpt:
model: gpt-3.5-turbo
description: |
"River Adventure" is a game where you need to cross a river by jumping on stones.
The game is over when you either fall into the water or reach the other side.
```

### ➤ Migration

There are some tools to make migration from other i18n solutions easier.
Expand Down
1 change: 0 additions & 1 deletion slang/bin/slang.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ void main(List<String> arguments) async {
case RunnerMode.edit:
break;
case RunnerMode.outdated:
print('Adding "OUTDATED" flag...');
break;
case RunnerMode.add:
print('Adding translation...');
Expand Down
1 change: 1 addition & 0 deletions slang/lib/builder/builder/raw_config_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class RawConfigBuilder {
?.toObfuscationConfig() ??
RawConfig.defaultObfuscationConfig,
imports: map['imports']?.cast<String>() ?? RawConfig.defaultImports,
rawMap: map,
);
}
}
Expand Down
93 changes: 93 additions & 0 deletions slang/lib/builder/model/raw_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ class RawConfig {
final ObfuscationConfig obfuscation;
final List<String> imports;

/// Used by external tools to access the raw config. (e.g. slang_gpt)
final Map<String, dynamic> rawMap;

RawConfig({
required this.baseLocale,
required this.fallbackStrategy,
Expand Down Expand Up @@ -100,8 +103,62 @@ class RawConfig {
required this.interfaces,
required this.obfuscation,
required this.imports,
required this.rawMap,
}) : fileType = _determineFileType(inputFilePattern);

RawConfig copyWith({
I18nLocale? baseLocale,
FallbackStrategy? fallbackStrategy,
String? inputFilePattern,
OutputFormat? outputFormat,
bool? localeHandling,
bool? flutterIntegration,
bool? namespaces,
CaseStyle? keyCase,
CaseStyle? keyMapCase,
bool? translationOverrides,
List<String>? maps,
PluralAuto? pluralAuto,
List<String>? pluralCardinal,
List<String>? pluralOrdinal,
List<ContextType>? contexts,
List<InterfaceConfig>? interfaces,
ObfuscationConfig? obfuscation,
}) {
return RawConfig(
baseLocale: baseLocale ?? this.baseLocale,
fallbackStrategy: fallbackStrategy ?? this.fallbackStrategy,
inputDirectory: inputDirectory,
inputFilePattern: inputFilePattern ?? this.inputFilePattern,
outputDirectory: outputDirectory,
outputFileName: outputFileName,
outputFormat: outputFormat ?? this.outputFormat,
localeHandling: localeHandling ?? this.localeHandling,
flutterIntegration: flutterIntegration ?? this.flutterIntegration,
namespaces: namespaces ?? this.namespaces,
translateVar: translateVar,
enumName: enumName,
translationClassVisibility: translationClassVisibility,
keyCase: keyCase ?? this.keyCase,
keyMapCase: keyMapCase ?? this.keyMapCase,
paramCase: paramCase,
stringInterpolation: stringInterpolation,
renderFlatMap: renderFlatMap,
renderTimestamp: renderTimestamp,
translationOverrides: translationOverrides ?? this.translationOverrides,
maps: maps ?? this.maps,
pluralAuto: pluralAuto ?? this.pluralAuto,
pluralParameter: pluralParameter,
pluralCardinal: pluralCardinal ?? this.pluralCardinal,
pluralOrdinal: pluralOrdinal ?? this.pluralOrdinal,
contexts: contexts ?? this.contexts,
interfaces: interfaces ?? this.interfaces,
obfuscation: obfuscation ?? this.obfuscation,
imports: imports,
rawMap: rawMap,
);
}

void validate() {
if (translationOverrides && !renderFlatMap) {
throw 'flat_map is deactivated but it is required by translation_overrides.';
Expand Down Expand Up @@ -175,4 +232,40 @@ class RawConfig {
print(' -> obfuscation: ${obfuscation.enabled ? 'enabled' : 'disabled'}');
print(' -> imports: $imports');
}

static final defaultLocale =
I18nLocale.fromString(RawConfig.defaultBaseLocale);

static final defaultConfig = RawConfig(
baseLocale: defaultLocale,
fallbackStrategy: RawConfig.defaultFallbackStrategy,
inputDirectory: RawConfig.defaultInputDirectory,
inputFilePattern: RawConfig.defaultInputFilePattern,
outputDirectory: RawConfig.defaultOutputDirectory,
outputFileName: RawConfig.defaultOutputFileName,
outputFormat: RawConfig.defaultOutputFormat,
localeHandling: RawConfig.defaultLocaleHandling,
flutterIntegration: RawConfig.defaultFlutterIntegration,
namespaces: RawConfig.defaultNamespaces,
translateVar: RawConfig.defaultTranslateVar,
enumName: RawConfig.defaultEnumName,
translationClassVisibility: RawConfig.defaultTranslationClassVisibility,
keyCase: RawConfig.defaultKeyCase,
keyMapCase: RawConfig.defaultKeyMapCase,
paramCase: RawConfig.defaultParamCase,
stringInterpolation: RawConfig.defaultStringInterpolation,
renderFlatMap: RawConfig.defaultRenderFlatMap,
translationOverrides: RawConfig.defaultTranslationOverrides,
renderTimestamp: RawConfig.defaultRenderTimestamp,
maps: RawConfig.defaultMaps,
pluralAuto: RawConfig.defaultPluralAuto,
pluralParameter: RawConfig.defaultPluralParameter,
pluralCardinal: RawConfig.defaultCardinal,
pluralOrdinal: RawConfig.defaultOrdinal,
contexts: RawConfig.defaultContexts,
interfaces: RawConfig.defaultInterfaces,
obfuscation: RawConfig.defaultObfuscationConfig,
imports: RawConfig.defaultImports,
rawMap: {},
);
}
38 changes: 38 additions & 0 deletions slang/lib/builder/utils/map_utils.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import 'package:collection/collection.dart';
import 'package:slang/builder/utils/node_utils.dart';

class MapUtils {
/// converts Map<dynamic, dynamic> to Map<String, dynamic> for all children
/// forcing all keys to be strings
Expand Down Expand Up @@ -285,6 +288,41 @@ class MapUtils {
// This should never be reached.
return false;
}

/// Removes all keys from [target] that also exist in [other].
static Map<String, dynamic> subtract({
required Map<String, dynamic> target,
required Map<String, dynamic> other,
}) {
final resultMap = <String, dynamic>{};
for (final entry in target.entries) {
final keyWithoutModifier = entry.key.withoutModifiers;
if (entry.value is! Map) {
// Add the entry if the key does not exist in the other map.
if (other.keys.firstWhereOrNull(
(k) => k.withoutModifiers == keyWithoutModifier) ==
null) {
resultMap[entry.key] = entry.value;
}
} else {
// Recursively subtract the map.
final otherKey = other.keys
.firstWhereOrNull((k) => k.withoutModifiers == keyWithoutModifier);
if (otherKey == null) {
resultMap[entry.key] = entry.value;
} else {
final subtracted = subtract(
target: entry.value,
other: other[otherKey],
);
if (subtracted.isNotEmpty) {
resultMap[entry.key] = subtracted;
}
}
}
}
return resultMap;
}
}

/// Helper function for [deepCast] handling lists
Expand Down
10 changes: 8 additions & 2 deletions slang/lib/runner/apply.dart
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ Future<void> _applyTranslationsForFile({
baseMap: baseTranslations,
newMap: newTranslations,
oldMap: parsedContent,
verbose: true,
);

FileUtils.writeFileOfType(
Expand All @@ -285,6 +286,7 @@ Map<String, dynamic> applyMapRecursive({
required Map<String, dynamic> baseMap,
required Map<String, dynamic> newMap,
required Map<String, dynamic> oldMap,
required bool verbose,
}) {
final resultMap = <String, dynamic>{};
final Set<String> overwrittenKeys = {}; // keys without modifiers
Expand Down Expand Up @@ -312,13 +314,16 @@ Map<String, dynamic> applyMapRecursive({
: throw 'In the base translations, "$key" is not a map.',
newMap: newEntry ?? {},
oldMap: oldMap[key] ?? {},
verbose: verbose,
);
}

if (newEntry != null) {
final split = key.split('(');
overwrittenKeys.add(split.first);
_printAdding(currPath, actualValue);
if (verbose) {
_printAdding(currPath, actualValue);
}
}
resultMap[key] = actualValue;
}
Expand Down Expand Up @@ -348,10 +353,11 @@ Map<String, dynamic> applyMapRecursive({
baseMap: {},
newMap: newEntry ?? {},
oldMap: oldMap[key],
verbose: verbose,
);
}

if (newEntry != null) {
if (verbose && newEntry != null) {
_printAdding(currPath, actualValue);
}
resultMap[key] = actualValue;
Expand Down
1 change: 0 additions & 1 deletion slang/test/integration/main/fallback_base_locale_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'package:slang/builder/model/i18n_locale.dart';
import 'package:slang/builder/model/translation_map.dart';
import 'package:test/test.dart';

import '../../util/config_utils.dart';
import '../../util/resources_utils.dart';

void main() {
Expand Down
1 change: 0 additions & 1 deletion slang/test/integration/main/json_multiple_files_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'package:slang/builder/model/i18n_locale.dart';
import 'package:slang/builder/model/translation_map.dart';
import 'package:test/test.dart';

import '../../util/config_utils.dart';
import '../../util/resources_utils.dart';

void main() {
Expand Down
1 change: 0 additions & 1 deletion slang/test/integration/main/no_flutter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'package:slang/builder/model/i18n_locale.dart';
import 'package:slang/builder/model/translation_map.dart';
import 'package:test/test.dart';

import '../../util/config_utils.dart';
import '../../util/resources_utils.dart';

void main() {
Expand Down
3 changes: 1 addition & 2 deletions slang/test/integration/main/no_locale_handling_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'package:slang/builder/model/i18n_locale.dart';
import 'package:slang/builder/model/translation_map.dart';
import 'package:test/test.dart';

import '../../util/config_utils.dart';
import '../../util/resources_utils.dart';

void main() {
Expand All @@ -22,7 +21,7 @@ void main() {
test('no locale handling', () {
final result = GeneratorFacade.generate(
rawConfig: RawConfigBuilder.fromYaml(buildYaml)!.copyWith(
renderLocaleHandling: false,
localeHandling: false,
),
baseName: 'translations',
translationMap: TranslationMap()
Expand Down
1 change: 0 additions & 1 deletion slang/test/integration/main/obfuscation_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'package:slang/builder/model/obfuscation_config.dart';
import 'package:slang/builder/model/translation_map.dart';
import 'package:test/test.dart';

import '../../util/config_utils.dart';
import '../../util/resources_utils.dart';

void main() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'package:slang/builder/model/i18n_locale.dart';
import 'package:slang/builder/model/translation_map.dart';
import 'package:test/test.dart';

import '../../util/config_utils.dart';
import '../../util/resources_utils.dart';

void main() {
Expand Down
3 changes: 1 addition & 2 deletions slang/test/integration/update.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import 'package:slang/builder/model/i18n_locale.dart';
import 'package:slang/builder/model/translation_map.dart';
import 'package:slang/builder/utils/file_utils.dart';

import '../util/config_utils.dart';
import '../util/resources_utils.dart';

/// To run this:
Expand Down Expand Up @@ -130,7 +129,7 @@ void generateNoFlutter(RawConfig buildConfig, String simple) {

void generateNoLocaleHandling(RawConfig buildConfig, String simple) {
final result = _generate(
rawConfig: buildConfig.copyWith(renderLocaleHandling: false),
rawConfig: buildConfig.copyWith(localeHandling: false),
baseName: 'translations',
translationMap: TranslationMap()
..addTranslations(
Expand Down
15 changes: 8 additions & 7 deletions slang/test/unit/builder/slang_file_collection_builder_test.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import 'package:slang/builder/builder/slang_file_collection_builder.dart';
import 'package:slang/builder/model/i18n_locale.dart';
import 'package:slang/builder/model/raw_config.dart';
import 'package:slang/builder/model/slang_file_collection.dart';
import 'package:test/test.dart';

import '../../util/config_utils.dart';

PlainTranslationFile _file(String path) {
return PlainTranslationFile(path: path, read: () => Future.value(''));
}
Expand All @@ -13,7 +12,8 @@ void main() {
group('SlangFileCollectionBuilder.fromFileModel', () {
test('should find base locale', () {
final model = SlangFileCollectionBuilder.fromFileModel(
config: baseConfig.copyWith(baseLocale: I18nLocale(language: 'de')),
config: RawConfig.defaultConfig
.copyWith(baseLocale: I18nLocale(language: 'de')),
files: [
_file('lib/i18n/strings.i18n.json'),
],
Expand All @@ -25,7 +25,8 @@ void main() {

test('should find locale in file names', () {
final model = SlangFileCollectionBuilder.fromFileModel(
config: baseConfig.copyWith(baseLocale: I18nLocale(language: 'en')),
config: RawConfig.defaultConfig
.copyWith(baseLocale: I18nLocale(language: 'en')),
files: [
_file('lib/i18n/strings.i18n.json'),
_file('lib/i18n/strings_de.i18n.json'),
Expand All @@ -42,7 +43,7 @@ void main() {

test('should find base locale with namespace', () {
final model = SlangFileCollectionBuilder.fromFileModel(
config: baseConfig.copyWith(
config: RawConfig.defaultConfig.copyWith(
baseLocale: I18nLocale(language: 'fr'),
namespaces: true,
),
Expand All @@ -58,7 +59,7 @@ void main() {

test('should find directory locale', () {
final model = SlangFileCollectionBuilder.fromFileModel(
config: baseConfig.copyWith(
config: RawConfig.defaultConfig.copyWith(
baseLocale: I18nLocale(language: 'de'),
namespaces: true,
),
Expand All @@ -83,7 +84,7 @@ void main() {

test('should ignore underscore if directory locale is used', () {
final model = SlangFileCollectionBuilder.fromFileModel(
config: baseConfig.copyWith(
config: RawConfig.defaultConfig.copyWith(
baseLocale: I18nLocale(language: 'de'),
inputFilePattern: '.yaml',
namespaces: true,
Expand Down
Loading

0 comments on commit e7e35f1

Please sign in to comment.