lokalise_rails 0.0.2.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -5
- data/README.md +71 -26
- data/lib/generators/lokalise_rails/install_generator.rb +2 -2
- data/lib/generators/templates/lokalise_rails_config.rb +30 -25
- data/lib/lokalise_rails.rb +44 -19
- data/lib/lokalise_rails/railtie.rb +2 -3
- data/lib/lokalise_rails/task_definition/base.rb +37 -6
- data/lib/lokalise_rails/task_definition/exporter.rb +80 -0
- data/lib/lokalise_rails/task_definition/importer.rb +57 -15
- data/lib/lokalise_rails/version.rb +2 -2
- data/lib/tasks/lokalise_rails_tasks.rake +14 -0
- data/lokalise_rails.gemspec +1 -1
- data/spec/dummy/config/application.rb +4 -2
- data/spec/dummy/config/lokalise_rails.rb +5 -0
- data/spec/lib/generators/lokalise_rails/install_generator_spec.rb +1 -1
- data/spec/lib/lokalise_rails/exporter_spec.rb +148 -0
- data/spec/lib/lokalise_rails/importer_spec.rb +22 -4
- data/spec/lib/lokalise_rails_spec.rb +27 -3
- data/spec/lib/tasks/export_task_spec.rb +7 -0
- data/spec/lib/tasks/import_task_spec.rb +34 -14
- data/spec/spec_helper.rb +9 -2
- data/spec/support/file_manager.rb +81 -0
- data/spec/support/rake_utils.rb +4 -0
- data/spec/support/spec_addons.rb +6 -0
- metadata +14 -20
- data/spec/dummy/config/initializers/lokalise_rails.rb +0 -29
- data/spec/support/file_utils.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8341e9150b8080050eb672077a415739178279e452ccad5d9f25bc7a24f83be
|
4
|
+
data.tar.gz: 4b6a74a12e62625d7bd6a41c8b46a93d029ef315551d4c6883375d9bf765aa72
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 13f5680ba0f8cfcd279929d7ab577712b1392760ca1d11460f6290d03d1ac9c4fe4915057656e3440cdda5a0958e5b230547549c9f2d4fe431c774fd7fe9ebd2
|
7
|
+
data.tar.gz: 4b98524742a8ed099f176351e073526db7e9fdae8e492318ac9f86c5b6ec8a7e650d9726a0b57571856e04a973e7aa9bcd7456aa9a6117e677d5039de33d1580
|
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,19 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
##
|
3
|
+
## 1.0.1 (14-Oct-20)
|
4
4
|
|
5
|
-
*
|
6
|
-
* Fix config template
|
5
|
+
* Minor bug fixes and spec updates
|
7
6
|
|
8
|
-
## 0.0
|
7
|
+
## 1.0.0 (01-Oct-20)
|
9
8
|
|
10
|
-
*
|
9
|
+
* Added export feature
|
10
|
+
* More convenient configuration and additional options
|
11
|
+
* Other major enhancements
|
12
|
+
|
13
|
+
## 0.2.0 (26-Sep-20)
|
14
|
+
|
15
|
+
* Allow to override options properly
|
16
|
+
|
17
|
+
## 0.1.0 (26-Sep-20)
|
18
|
+
|
19
|
+
* Initial (proper) release
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# LokaliseRails
|
2
2
|
|
3
|
-
|
3
|
+
[](https://badge.fury.io/rb/lokalise_rails)
|
4
4
|
[](https://travis-ci.org/bodrovis/lokalise_rails)
|
5
5
|
[](https://codecov.io/gh/bodrovis/lokalise_rails)
|
6
6
|
|
@@ -10,34 +10,39 @@ This gem provides [Lokalise](http://lokalise.com) integration for Ruby on Rails
|
|
10
10
|
|
11
11
|
### Requirements
|
12
12
|
|
13
|
-
This gem requires Ruby 2.5+ and Rails 5.1+. It might work with older versions of Rails though. You will also need to setup a Lokalise account and a translation project. Finally, you will need to generate a read/write API token at your Lokalise profile.
|
13
|
+
This gem requires Ruby 2.5+ and Rails 5.1+. It might work with older versions of Rails though. You will also need to [setup a Lokalise account](https://app.lokalise.com/signup) and create a [translation project](https://docs.lokalise.com/en/articles/1400460-projects). Finally, you will need to generate a [read/write API token](https://docs.lokalise.com/en/articles/1929556-api-tokens) at your Lokalise profile.
|
14
14
|
|
15
15
|
### Installation
|
16
16
|
|
17
|
-
Add the gem to your `Gemfile
|
17
|
+
Add the gem to your `Gemfile`:
|
18
18
|
|
19
19
|
```ruby
|
20
20
|
gem 'lokalise_rails'
|
21
21
|
```
|
22
22
|
|
23
|
-
and run
|
23
|
+
and run:
|
24
24
|
|
25
25
|
```
|
26
26
|
bundle install
|
27
27
|
rails g lokalise_rails:install
|
28
28
|
```
|
29
29
|
|
30
|
-
The latter command will generate a new
|
30
|
+
The latter command will generate a new config file `config/lokalise_rails.rb` looking like this:
|
31
31
|
|
32
32
|
```ruby
|
33
33
|
require 'lokalise_rails'
|
34
34
|
|
35
|
-
LokaliseRails.
|
36
|
-
|
37
|
-
|
35
|
+
LokaliseRails.config do |c|
|
36
|
+
c.api_token = ENV['LOKALISE_API_TOKEN']
|
37
|
+
c.project_id = ENV['LOKALISE_PROJECT_ID']
|
38
|
+
|
39
|
+
# ...other options
|
40
|
+
end
|
38
41
|
```
|
39
42
|
|
40
|
-
You have to provide `api_token` and `project_id` to proceed.
|
43
|
+
You have to provide `api_token` and `project_id` to proceed. `project_id` can be found in your Lokalise project settings.
|
44
|
+
|
45
|
+
[Other options can be customized as well (see below)](https://github.com/bodrovis/lokalise_rails#import-settings) but they have sensible defaults.
|
41
46
|
|
42
47
|
## Importing translations from Lokalise
|
43
48
|
|
@@ -47,31 +52,46 @@ To import translations from the specified Lokalise project to your Rails app, ru
|
|
47
52
|
rails lokalise_rails:import
|
48
53
|
```
|
49
54
|
|
50
|
-
Please note that any
|
55
|
+
Please note that any duplicating files inside the `locales` directory (or any other directory that you've specified in the options) will be overwritten! You may enable [safe mode](https://github.com/bodrovis/lokalise_rails#import-settings) to check whether the folder is empty or not.
|
51
56
|
|
52
|
-
##
|
57
|
+
## Exporting translations to Lokalise
|
53
58
|
|
54
|
-
|
59
|
+
To export translations from your Rails app to the specified Lokalise project, run the following command:
|
55
60
|
|
56
|
-
|
61
|
+
```
|
62
|
+
rails lokalise_rails:export
|
63
|
+
```
|
57
64
|
|
58
|
-
|
59
|
-
|
60
|
-
|
65
|
+
## Running tasks programmatically
|
66
|
+
|
67
|
+
You can also run the import and export tasks from the Rails app:
|
61
68
|
|
62
69
|
```ruby
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
+
require "#{Rails.root}/config/lokalise_rails.rb"
|
71
|
+
|
72
|
+
# Import the files:
|
73
|
+
result = LokaliseRails::TaskDefinition::Importer.import!
|
74
|
+
# `result` contains a boolean value with the result of the operation
|
75
|
+
|
76
|
+
# Export the files:
|
77
|
+
processes = LokaliseRails::TaskDefinition::Exporter.export!
|
78
|
+
# `processes` contains a list of queued background processes
|
70
79
|
```
|
71
80
|
|
81
|
+
## Configuration
|
82
|
+
|
83
|
+
Options are specified in the `config/lokalise_rails.rb` file.
|
84
|
+
|
85
|
+
### Global settings
|
86
|
+
|
87
|
+
* `api_token` (`string`, required) - Lokalise API token with read/write permissions.
|
88
|
+
* `project_id` (`string`, required) - Lokalise project ID. You must have import/export permissions in the specified project.
|
89
|
+
* `locales_path` (`string`) - path to the directory with your translation files. Defaults to `"#{Rails.root}/config/locales"`.
|
90
|
+
* `file_ext_regexp` (`regexp`) - regular expression applied to file extensions to determine which files should be imported and exported. Defaults to `/\.ya?ml\z/i` (YAML files).
|
91
|
+
|
72
92
|
### Import settings
|
73
93
|
|
74
|
-
* `
|
94
|
+
* `import_opts` (`hash`) - options that will be passed to Lokalise API when downloading translations to your app. Here are the default options:
|
75
95
|
|
76
96
|
```ruby
|
77
97
|
{
|
@@ -84,8 +104,33 @@ end
|
|
84
104
|
}
|
85
105
|
```
|
86
106
|
|
87
|
-
Full list of available options [can be found
|
88
|
-
* `
|
107
|
+
Full list of available import options [can be found in the official API documentation](https://app.lokalise.com/api2docs/curl/#transition-download-files-post).
|
108
|
+
* `import_safe_mode` (`boolean`) - default to `false`. When this option is enabled, the import task will check whether the directory set with `locales_path` is empty or not. If it is not empty, you will be prompted to continue.
|
109
|
+
|
110
|
+
### Export settings
|
111
|
+
|
112
|
+
* `export_opts` (`hash`) - options that will be passed to Lokalise API when uploading translations. Full list of available export options [can be found in the official documentation](https://app.lokalise.com/api2docs/curl/#transition-download-files-post). By default, the following options are provided:
|
113
|
+
+ `data` (`string`, required) - base64-encoded contents of the translation file.
|
114
|
+
+ `filename` (`string`, required) - translation file name. If the file is stored under a subdirectory (for example, `nested/en.yml` inside the `locales/` directory), the whole path acts as a name. Later when importing files with such names, they will be placed into the proper subdirectories.
|
115
|
+
+ `lang_iso` (`string`, required) - language ISO code which is determined using the root key inside your YAML file. For example, in this case the `lang_iso` is `en_US`:
|
116
|
+
|
117
|
+
```yaml
|
118
|
+
en_US:
|
119
|
+
my_key: "my value"
|
120
|
+
```
|
121
|
+
|
122
|
+
**Please note** that if your Lokalise project does not have a language with the specified `lang_iso` code, the export will fail.
|
123
|
+
|
124
|
+
* `skip_file_export` (`lambda` or `proc`) - specify additional exclusion criteria for the exported files. By default, the rake task will ignore all non-file entries and all files with improper extensions (the latter is controlled by the `file_ext_regexp`). Lambda passed to this option should accept a single argument which is full path to the file (instance of the [`Pathname` class](https://ruby-doc.org/stdlib-2.7.1/libdoc/pathname/rdoc/Pathname.html)). For example, to exclude all files that have `fr` part in their names, add the following config:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
c.skip_file_export = ->(file) { f.split[1].to_s.include?('fr') }
|
128
|
+
```
|
129
|
+
|
130
|
+
## Running tests
|
131
|
+
|
132
|
+
1. Copypaste `.env.example` file as `.env`. Put your Lokalise API token and project ID inside. The `.env` file is excluded from version control so your data is safe. All in all, we use pre-recorded VCR cassettes, so the actual API requests won’t be sent. However, providing at least some values is required.
|
133
|
+
2. Run `rspec .`. Observe test results and code coverage.
|
89
134
|
|
90
135
|
## License
|
91
136
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'rails/generators'
|
4
4
|
|
5
|
-
|
5
|
+
module LokaliseRails
|
6
6
|
module Generators
|
7
7
|
class InstallGenerator < Rails::Generators::Base
|
8
8
|
source_root File.expand_path('../templates', __dir__)
|
@@ -10,7 +10,7 @@ class LokaliseRails
|
|
10
10
|
desc 'Creates a LokaliseRails config file.'
|
11
11
|
|
12
12
|
def copy_config
|
13
|
-
template 'lokalise_rails_config.rb', "#{Rails.root}/config/
|
13
|
+
template 'lokalise_rails_config.rb', "#{Rails.root}/config/lokalise_rails.rb"
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -2,28 +2,33 @@
|
|
2
2
|
|
3
3
|
require 'lokalise_rails'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
#
|
12
|
-
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
|
19
|
-
#
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
#
|
24
|
-
#
|
25
|
-
|
26
|
-
#
|
27
|
-
#
|
28
|
-
|
29
|
-
#
|
5
|
+
LokaliseRails.config do |c|
|
6
|
+
# These are mandatory options that you must set before running rake tasks:
|
7
|
+
# c.api_token = ENV['LOKALISE_API_TOKEN']
|
8
|
+
# c.project_id = ENV['LOKALISE_PROJECT_ID']
|
9
|
+
|
10
|
+
# Provide a custom path to the directory with your translation files:
|
11
|
+
# c.locales_path = "#{Rails.root}/config/locales"
|
12
|
+
|
13
|
+
# Import options have the following defaults:
|
14
|
+
# c.import_opts = {
|
15
|
+
# format: 'yaml',
|
16
|
+
# placeholder_format: :icu,
|
17
|
+
# yaml_include_root: true,
|
18
|
+
# original_filenames: true,
|
19
|
+
# directory_prefix: '',
|
20
|
+
# indentation: '2sp'
|
21
|
+
# }
|
22
|
+
|
23
|
+
# Safe mode for imports is disabled by default:
|
24
|
+
# c.import_safe_mode = false
|
25
|
+
|
26
|
+
# Additional export options (only filename, contents, and lang_iso params are provided by default)
|
27
|
+
# c.export_opts = {}
|
28
|
+
|
29
|
+
# Provide additional file exclusion criteria for exports (by default, any file with the proper extension will be exported)
|
30
|
+
# c.skip_file_export = ->(file) { file.split[1].to_s.include?('fr') }
|
31
|
+
|
32
|
+
# Regular expression to use when choosing the files to extract from the downloaded archive and upload to Lokalise
|
33
|
+
# c.file_ext_regexp = /\.ya?ml\z/i
|
34
|
+
end
|
data/lib/lokalise_rails.rb
CHANGED
@@ -2,29 +2,54 @@
|
|
2
2
|
|
3
3
|
require 'lokalise_rails/task_definition/base'
|
4
4
|
require 'lokalise_rails/task_definition/importer'
|
5
|
+
require 'lokalise_rails/task_definition/exporter'
|
5
6
|
|
6
|
-
|
7
|
-
@project_id = nil
|
8
|
-
@import_opts = {
|
9
|
-
format: 'yaml',
|
10
|
-
placeholder_format: :icu,
|
11
|
-
yaml_include_root: true,
|
12
|
-
original_filenames: true,
|
13
|
-
directory_prefix: '',
|
14
|
-
indentation: '2sp'
|
15
|
-
}
|
16
|
-
# @export_opts = {
|
17
|
-
#
|
18
|
-
# }
|
19
|
-
@import_safe_mode = false
|
20
|
-
@api_token = nil
|
21
|
-
|
7
|
+
module LokaliseRails
|
22
8
|
class << self
|
23
|
-
attr_accessor :
|
24
|
-
|
9
|
+
attr_accessor :api_token, :project_id
|
10
|
+
attr_writer :import_opts, :import_safe_mode, :export_opts, :locales_path,
|
11
|
+
:file_ext_regexp, :skip_file_export
|
12
|
+
|
13
|
+
# Main interface to provide configuration options for rake tasks
|
14
|
+
def config
|
15
|
+
yield self
|
16
|
+
end
|
25
17
|
|
18
|
+
# Full path to directory with translation files
|
26
19
|
def locales_path
|
27
|
-
"#{Rails.root}/config/locales"
|
20
|
+
@locales_path || "#{Rails.root}/config/locales"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Regular expression used to select translation files with proper extensions
|
24
|
+
def file_ext_regexp
|
25
|
+
@file_ext_regexp || /\.ya?ml\z/i
|
26
|
+
end
|
27
|
+
|
28
|
+
# Options for import rake task
|
29
|
+
def import_opts
|
30
|
+
@import_opts || {
|
31
|
+
format: 'yaml',
|
32
|
+
placeholder_format: :icu,
|
33
|
+
yaml_include_root: true,
|
34
|
+
original_filenames: true,
|
35
|
+
directory_prefix: '',
|
36
|
+
indentation: '2sp'
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
# Options for export rake task
|
41
|
+
def export_opts
|
42
|
+
@export_opts || {}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Enables safe mode for import. When enabled, will check whether the target folder is empty or not
|
46
|
+
def import_safe_mode
|
47
|
+
@import_safe_mode.nil? ? false : @import_safe_mode
|
48
|
+
end
|
49
|
+
|
50
|
+
# Additional file skip criteria to apply when performing export
|
51
|
+
def skip_file_export
|
52
|
+
@skip_file_export || ->(_) { false }
|
28
53
|
end
|
29
54
|
end
|
30
55
|
end
|
@@ -1,12 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rubygems'
|
4
3
|
require 'rake'
|
5
4
|
|
6
|
-
|
5
|
+
module LokaliseRails
|
7
6
|
class Railtie < Rails::Railtie
|
8
7
|
rake_tasks do
|
9
|
-
load
|
8
|
+
load 'tasks/lokalise_rails_tasks.rake'
|
10
9
|
end
|
11
10
|
end
|
12
11
|
end
|
@@ -1,17 +1,48 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'ruby-lokalise-api'
|
4
|
-
require '
|
4
|
+
require 'pathname'
|
5
5
|
|
6
|
-
|
6
|
+
module LokaliseRails
|
7
7
|
module TaskDefinition
|
8
8
|
class Base
|
9
9
|
class << self
|
10
|
-
|
11
|
-
return [false, 'Project ID is not set! Aborting...'] unless LokaliseRails.project_id
|
12
|
-
return [false, 'Lokalise API token is not set! Aborting...'] unless LokaliseRails.api_token
|
10
|
+
attr_writer :api_client
|
13
11
|
|
14
|
-
|
12
|
+
# Creates a Lokalise API client
|
13
|
+
#
|
14
|
+
# @return [Lokalise::Client]
|
15
|
+
def api_client
|
16
|
+
@api_client ||= ::Lokalise.client LokaliseRails.api_token
|
17
|
+
end
|
18
|
+
|
19
|
+
# Checks task options
|
20
|
+
#
|
21
|
+
# @return Array
|
22
|
+
def opt_errors
|
23
|
+
errors = []
|
24
|
+
errors << 'Project ID is not set! Aborting...' if LokaliseRails.project_id.nil? || LokaliseRails.project_id.empty?
|
25
|
+
errors << 'Lokalise API token is not set! Aborting...' if LokaliseRails.api_token.nil? || LokaliseRails.api_token.empty?
|
26
|
+
errors
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Checks whether the provided file has a proper extension as dictated by the `file_ext_regexp` option
|
32
|
+
#
|
33
|
+
# @return Boolean
|
34
|
+
# @param raw_path [String, Pathname]
|
35
|
+
def proper_ext?(raw_path)
|
36
|
+
path = raw_path.is_a?(Pathname) ? raw_path : Pathname.new(raw_path)
|
37
|
+
LokaliseRails.file_ext_regexp.match? path.extname
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns directory and filename for the given entry
|
41
|
+
#
|
42
|
+
# @return Array
|
43
|
+
# @param entry [String]
|
44
|
+
def subdir_and_filename_for(entry)
|
45
|
+
Pathname.new(entry).split
|
15
46
|
end
|
16
47
|
end
|
17
48
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module LokaliseRails
|
6
|
+
module TaskDefinition
|
7
|
+
class Exporter < Base
|
8
|
+
class << self
|
9
|
+
# Performs translation file export from Rails to Lokalise and returns an array of queued processes
|
10
|
+
#
|
11
|
+
# @return [Array]
|
12
|
+
def export!
|
13
|
+
errors = opt_errors
|
14
|
+
|
15
|
+
if errors.any?
|
16
|
+
errors.each { |e| $stdout.puts e }
|
17
|
+
return errors
|
18
|
+
end
|
19
|
+
|
20
|
+
queued_processes = []
|
21
|
+
each_file do |full_path, relative_path|
|
22
|
+
queued_processes << api_client.upload_file(
|
23
|
+
LokaliseRails.project_id, opts(full_path, relative_path)
|
24
|
+
)
|
25
|
+
rescue StandardError => e
|
26
|
+
$stdout.puts "Error while trying to upload #{full_path}: #{e.inspect}"
|
27
|
+
end
|
28
|
+
|
29
|
+
$stdout.print 'Task complete!'
|
30
|
+
|
31
|
+
queued_processes
|
32
|
+
end
|
33
|
+
|
34
|
+
# Processes each translation file in the specified directory
|
35
|
+
def each_file
|
36
|
+
return unless block_given?
|
37
|
+
|
38
|
+
loc_path = LokaliseRails.locales_path
|
39
|
+
Dir["#{loc_path}/**/*"].sort.each do |f|
|
40
|
+
full_path = Pathname.new f
|
41
|
+
|
42
|
+
next unless file_matches_criteria? full_path
|
43
|
+
|
44
|
+
relative_path = full_path.relative_path_from Pathname.new(loc_path)
|
45
|
+
|
46
|
+
yield full_path, relative_path
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Generates export options
|
51
|
+
#
|
52
|
+
# @return [Hash]
|
53
|
+
# @param full_p [Pathname]
|
54
|
+
# @param relative_p [Pathname]
|
55
|
+
def opts(full_p, relative_p)
|
56
|
+
content = File.read full_p
|
57
|
+
|
58
|
+
lang_iso = YAML.safe_load(content)&.keys&.first
|
59
|
+
|
60
|
+
initial_opts = {
|
61
|
+
data: Base64.strict_encode64(content.strip),
|
62
|
+
filename: relative_p,
|
63
|
+
lang_iso: lang_iso
|
64
|
+
}
|
65
|
+
|
66
|
+
initial_opts.merge LokaliseRails.export_opts
|
67
|
+
end
|
68
|
+
|
69
|
+
# Checks whether the specified file has to be processed or not
|
70
|
+
#
|
71
|
+
# @return [Boolean]
|
72
|
+
# @param full_path [Pathname]
|
73
|
+
def file_matches_criteria?(full_path)
|
74
|
+
full_path.file? && proper_ext?(full_path) &&
|
75
|
+
!LokaliseRails.skip_file_export.call(full_path)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|