|
1 | | -# New Package for Stacked |
2 | | - |
3 | | -Created from the `package-template`. |
4 | | - |
5 | | -After creating the repository, proceed with the following instructions: |
6 | | - |
7 | | -- Update the repository settings to adhere to the conventions: |
8 | | - - General: |
9 | | - - No Wikis |
10 | | - - No Issues |
11 | | - - No Sponsorships |
12 | | - - Preserve this repository |
13 | | - - No Discussions |
14 | | - - No Projects |
15 | | - - Don't allow merge commits |
16 | | - - Allow squash merging with default commit message set to "Default to pull request title and commit details" |
17 | | - - Don't allow rebase merging |
18 | | - - Always suggest updating pull requests |
19 | | - - Allow auto-merge |
20 | | - - Automatically delete head branches |
21 | | - - Branch protection rule (`main`): |
22 | | - - Require a pull request before merging |
23 | | - - Dismiss stale pull request approvals when new commits are pushed |
24 | | - - Allow specified actors to bypass required pull requests -> `Dane Mackier` (or whoever is the current owner of the personal access token in the organization secrets `REPO_DEPLOYMENT_TOKEN`) |
25 | | - - Require status checks to pass before merging |
26 | | - - Require branches to be up to date before merging |
27 | | - - Add status check `Linting and Testing` (to select this, the workflow must have been run at least once. This can be done manually since the workflow has "workflow_dispatch" as a trigger) |
28 | | - - Require conversation resolution before merging |
29 | | - - Require linear history |
30 | | -- Create the flutter package with `flutter create -t package --project-name NAME .` |
31 | | -- Update the content in the `README` file. |
| 1 | +# Stacked Localisation [Not ready for production] |
| 2 | + |
| 3 | +A localisation solution specifically built for using with the [stacked package](https://pub.dev/packages/stacked) for state management. This comes in the form of a localisation service accessible to the ViewModels and other services which takes in a localisation key and returns the string (if any), as provided by the files that define the strings for each language. As all the other solutions provided by [FilledStacks](https://www.youtube.com/filledstacks) this solution aims to reduce the amount of code required for basic language setup, make it easier to understand and make the code more readable in the process. All of this is in hopes of creating a more maintainable code base to work with. |
| 4 | + |
| 5 | +## How does it work |
| 6 | + |
| 7 | +The localisation package is very simple. It provides you with a service from which you can request a translated string using a key. The keys map directly to your language files that should be placed in `assets/lang` as json files. The `stacked_localisation_generator` will then generate code for the keys defined in your language file. This will allow you to safely request keys without manually maintaining any of the keys. |
| 8 | + |
| 9 | +## Setup |
| 10 | + |
| 11 | +To use the localisation service there's a few things that has to be done. We start off by adding the stacked_localisation and the stacked_localisation_generator package (and build_runner if you don't already have it). |
| 12 | + |
| 13 | +```yaml |
| 14 | +dependencies: |
| 15 | + ... |
| 16 | + stacked_localisation: |
| 17 | + |
| 18 | +dev_dependencies: |
| 19 | + ... |
| 20 | + build_runner: |
| 21 | + stacked_localisation_generator: |
| 22 | +``` |
| 23 | +
|
| 24 | +Then we will add a new asset entry into pubspec.yaml that points to the `assets/lang` folder (You will create this folder if it doesn't exist). |
| 25 | + |
| 26 | +```yaml |
| 27 | +assets: |
| 28 | + - assets/lang/ |
| 29 | +``` |
| 30 | + |
| 31 | +Then we will create the folders mentioned above and place your strings for a different language in there. Create a folder called assets in the root folder and inside that folder create a new folder called lang. Then create a new file inside it called en.json or en.yaml and place the following JSON in there. |
| 32 | + |
| 33 | +```json |
| 34 | +{ |
| 35 | + "HomeView": { |
| 36 | + "title": "This is my Home", |
| 37 | + "subtitle": "I live in this Home" |
| 38 | + } |
| 39 | +} |
| 40 | +``` |
| 41 | + |
| 42 | +or for YAML |
| 43 | + |
| 44 | +```yaml |
| 45 | +HomeView: |
| 46 | + title: This is my Home |
| 47 | + subtitle: I live in this Home |
| 48 | +``` |
| 49 | + |
| 50 | +Then run `flutter pub run build_runner build --delete-conflicting-outputs` to generate the localisation_string_keys.dart file. This file will look like this. |
| 51 | + |
| 52 | +```dart |
| 53 | +/// This code is generated. DO NOT edit by hand |
| 54 | +
|
| 55 | +class HomeViewStrings { |
| 56 | + static String title = 'HomeView.title'; |
| 57 | + static String subtitle = 'HomeView.subtitle'; |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +It takes the name of the parent view and adds the word "Strings" behind it. This turns "HomeView" into "HomeViewStrings" and creates a property for each string value in that map. |
| 62 | + |
| 63 | +### Setup in code |
| 64 | + |
| 65 | +On the first line of the main function call the line `WidgetsFlutterBinding.ensureInitialized();` is required because we are making use of some plugins before the app runs which initialises all the plugin bindings. We will also change the main function to a Future and change our setupLocator function to a future as well and await on the setup call. |
| 66 | + |
| 67 | +```dart |
| 68 | +Future main() async { |
| 69 | + WidgetsFlutterBinding.ensureInitialized(); |
| 70 | + await setupLocator(); |
| 71 | + runApp(MyApp()); |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +Then you can open up your setup_locator.dart file and create an instance of the `LocationServices` then await the initialise call before registering it as a singleton. |
| 76 | + |
| 77 | +```dart |
| 78 | +import 'package:get_it/get_it.dart'; |
| 79 | +import 'package:stacked_localisation/stacked_localisation.dart'; |
| 80 | +import 'package:stacked_services/stacked_services.dart'; |
| 81 | +
|
| 82 | +GetIt locator = GetIt.instance; |
| 83 | +
|
| 84 | +Future setupLocator() async { |
| 85 | + var localisationService = await LocalisationService.getInstance(); |
| 86 | + locator.registerSingleton(localisationService); |
| 87 | +
|
| 88 | + locator.registerLazySingleton(() => NavigationService()); |
| 89 | + ... |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +### Usage in Code |
| 94 | + |
| 95 | +The initialisation functionality will load the strings into the `LocalisationService` which is now accessible. Create a new view file, or in your current view file make the following adjustments. |
| 96 | + |
| 97 | +```dart |
| 98 | +class HomeView extends StatelessWidget { |
| 99 | + const HomeView({Key key}) : super(key: key); |
| 100 | +
|
| 101 | + @override |
| 102 | + Widget build(BuildContext context) { |
| 103 | + return ViewModelBuilder<HomeViewModel>.reactive( |
| 104 | + builder: (context, model, child) => Scaffold( |
| 105 | + appBar: AppBar( |
| 106 | + title: Text(model.translate(HomeViewStrings.title)), |
| 107 | + ), |
| 108 | + body: Column( |
| 109 | + mainAxisSize: MainAxisSize.max, |
| 110 | + mainAxisAlignment: MainAxisAlignment.center, |
| 111 | + children: <Widget>[ |
| 112 | + Text(model.translate( |
| 113 | + HomeViewStrings.subtitle, |
| 114 | + )) |
| 115 | + ], |
| 116 | + ), |
| 117 | + ), |
| 118 | + viewModelBuilder: () => HomeViewModel(), |
| 119 | + ); |
| 120 | + } |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +You'll see the usage of the translate function on the model. Lets add that functionality. Open up your `ViewModel` file and add the `LocalisedClass` mixin to your `ViewModel` definition. |
| 125 | + |
| 126 | +```dart |
| 127 | +class HomeViewModel extends BaseViewModel with LocalisedClass {} |
| 128 | +``` |
| 129 | + |
| 130 | +This provides you with a translate function that will return the value associated with the key. That's it for the basic usage. |
| 131 | + |
| 132 | +## Outside of the code |
| 133 | + |
| 134 | +The language files added into the lang folder can be specific en_US.json or en_UK.json or it can be general en.json which will ensure all localisations starting with en will be given the strings defined in the en.json file. Another thing with the language files is to make sure all the files has the same strings. The localisation keys will be generated using the file that's first in the lang folder. If it's missing strings you won't be able to access them through the string key classes. |
| 135 | + |
| 136 | +## Dynamic values |
| 137 | + |
| 138 | +There is also support for dynamic values in the translations which are for values that you'd like to replace in the app. This is implemented through positional replacements. |
| 139 | + |
| 140 | +```json |
| 141 | +{ |
| 142 | + "CounterView": { |
| 143 | + "timesCounted": "You have tapped {0} times" |
| 144 | + } |
| 145 | +} |
| 146 | +``` |
| 147 | + |
| 148 | +Can be used using the following code |
| 149 | + |
| 150 | +```dart |
| 151 | +translate(CounterViewStrings.timesCounted, replacements: [9]); // Returns 'You have tapped 9 times' |
| 152 | +``` |
| 153 | + |
| 154 | +The replacements correlate to the index in the brackets so you can add multiple replacements for the same index or different values. An implementation of named replacements will be added if there is a need for it. |
| 155 | + |
| 156 | +## More Code |
| 157 | + |
| 158 | +The `LocalisedClass` mixin can be used in services as well, the exact same way. Which will allow you to throw localised exceptions or show localised dialogs easily as well. |
| 159 | + |
| 160 | +```dart |
| 161 | +throw Exception(translate(ApiStrings.serialisationError)); |
| 162 | +
|
| 163 | +// or |
| 164 | +
|
| 165 | +_dialogSerice.showDialog(title: translate(HomeView.favoriteAddedTitle)); |
| 166 | +``` |
| 167 | + |
| 168 | +If you have any requests, questions or pointers you can file an issue or [head over to our slack](https://join.slack.com/t/filledstacks/shared_invite/zt-8hae7yly-vjZX3sW5twN9v7DBlTsgrQ) and chat to use directly about improvements or just code in general. |
| 169 | + |
| 170 | +## Upcoming Features |
| 171 | + |
| 172 | +- [ ] Reload the language strings without having the restart the application. This will require adding something like the [LifeCycle manager shown here](https://youtu.be/NfvA-7-HzYk) to call the initialise function again which will load up the new strings and place them into memory. |
0 commit comments