imap-backup 16.4.2 → 16.5.0
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/bin/imap-backup +3 -0
- data/docs/TODO.md +16 -0
- data/docs/i18n.md +153 -0
- data/imap-backup.gemspec +3 -0
- data/lib/imap/backup/cli/helpers.rb +7 -9
- data/lib/imap/backup/cli/local/check.rb +1 -1
- data/lib/imap/backup/cli/local.rb +2 -2
- data/lib/imap/backup/cli/remote.rb +5 -2
- data/lib/imap/backup/cli/restore.rb +1 -1
- data/lib/imap/backup/cli/stats.rb +5 -5
- data/lib/imap/backup/cli/transfer.rb +14 -15
- data/lib/imap/backup/cli/utils.rb +6 -3
- data/lib/imap/backup/cli.rb +1 -1
- data/lib/imap/backup/configuration.rb +4 -7
- data/lib/imap/backup/locales/en.yml +166 -0
- data/lib/imap/backup/locales/it.yml +167 -0
- data/lib/imap/backup/setup/account/header.rb +18 -18
- data/lib/imap/backup/setup/account.rb +28 -23
- data/lib/imap/backup/setup/asker.rb +9 -5
- data/lib/imap/backup/setup/backup_path.rb +4 -4
- data/lib/imap/backup/setup/folder_chooser.rb +9 -11
- data/lib/imap/backup/setup/global_options/download_strategy_chooser.rb +13 -41
- data/lib/imap/backup/setup/global_options.rb +11 -6
- data/lib/imap/backup/setup.rb +8 -7
- data/lib/imap/backup/translator.rb +48 -0
- data/lib/imap/backup/version.rb +2 -2
- metadata +34 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d4ce42bf0d3de92eaa3355305f422bdccaf627e4d9cc31cb14c128417d2b87d
|
|
4
|
+
data.tar.gz: 513fc795468450786093972b7ad3fc5ec024a0ed86f11728443342dd41b04980
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 460cc300cb717a129a440a3d7c542475ed6122f6dfc55e1b643c707e6575450c35d5c331b328596763b87a5426a2ce5f15a27830d739d772dfd14ae1f7b75a8b
|
|
7
|
+
data.tar.gz: 20917771c877b133363f7d192df3293a9eb4fd234e23aee7f9018365e9caed492cc03c283526a069260f5091333a50f0b9c011a59de96d6027cb510e8cf5d6dc
|
data/bin/imap-backup
CHANGED
data/docs/TODO.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Add Internationalization (i18n) Support
|
|
2
|
+
|
|
3
|
+
Status: [x]
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Add internationalization support to make imap-backup accessible to non-English-speaking users. The primary focus should be on the menu-driven setup interface, which contains the most user-facing text. This will allow the application to display messages and prompts in the user's preferred language based on locale environment variables (LANGUAGE, LANG, LC_*).
|
|
8
|
+
|
|
9
|
+
## Technical Specifics
|
|
10
|
+
|
|
11
|
+
- Use the `locale` and `i18n` gems
|
|
12
|
+
- Override the `locale` gem's behaviour when LANG=C (use `:en`)
|
|
13
|
+
- Store translations under `lib/imap/backup/locales`
|
|
14
|
+
- Prioritize translating the menu-driven setup interface (`lib/imap/backup/setup.rb` and related files)
|
|
15
|
+
- Extract all user-facing strings into translation files with locale "en"
|
|
16
|
+
- Add Italian translations
|
data/docs/i18n.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Internationalization (i18n)
|
|
2
|
+
|
|
3
|
+
imap-backup supports multiple languages through internationalization (i18n). This document explains how the system works and how to contribute translations.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The application automatically detects the user's preferred language from environment variables and displays messages in that language. English is the default fallback language.
|
|
8
|
+
|
|
9
|
+
## How Locale Detection Works
|
|
10
|
+
|
|
11
|
+
The i18n system uses the `locale` gem to detect the user's preferred language by checking these environment variables in order:
|
|
12
|
+
|
|
13
|
+
1. `LANGUAGE`
|
|
14
|
+
2. `LANG`
|
|
15
|
+
3. `LC_ALL`
|
|
16
|
+
4. `LC_MESSAGES`
|
|
17
|
+
5. Other `LC_*` variables
|
|
18
|
+
|
|
19
|
+
Special case: When `LANG=C` is set (common in minimal environments), the system uses English (`:en`).
|
|
20
|
+
|
|
21
|
+
The system selects the first detected locale that has a translation file available.
|
|
22
|
+
|
|
23
|
+
## Translation Files
|
|
24
|
+
|
|
25
|
+
Translation files are stored in YAML format at:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
lib/imap/backup/locales/
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Each language has its own file named with the ISO 639-1 language code:
|
|
32
|
+
|
|
33
|
+
- `en.yml` - English (default)
|
|
34
|
+
- `it.yml` - Italian
|
|
35
|
+
|
|
36
|
+
## File Structure
|
|
37
|
+
|
|
38
|
+
Translation files use a hierarchical structure with nested keys. Here's an example:
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
---
|
|
42
|
+
en:
|
|
43
|
+
setup:
|
|
44
|
+
main_menu:
|
|
45
|
+
title: "Main Menu"
|
|
46
|
+
choose_action: "Choose an action"
|
|
47
|
+
add_account: "add account"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Using Translations in Code
|
|
51
|
+
|
|
52
|
+
Translations are accessed using the `I18n.t` method with dot-separated keys:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
I18n.t("setup.main_menu.title") # => "Main Menu"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Interpolation
|
|
59
|
+
|
|
60
|
+
Some strings include variables using the `%{variable}` syntax:
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
en:
|
|
64
|
+
setup:
|
|
65
|
+
account:
|
|
66
|
+
choose_folders: "choose folders to %{action}"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
In code:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
I18n.t("setup.account.choose_folders", action: "backup")
|
|
73
|
+
# => "choose folders to backup"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Contributing Translations
|
|
77
|
+
|
|
78
|
+
### Adding a New Language
|
|
79
|
+
|
|
80
|
+
To add support for a new language:
|
|
81
|
+
|
|
82
|
+
1. **Create a new locale file** in `lib/imap/backup/locales/` using the ISO 639-1 language code (e.g., `fr.yml` for French).
|
|
83
|
+
|
|
84
|
+
2. **Copy the structure from `en.yml`** to ensure all keys are present:
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
---
|
|
88
|
+
fr:
|
|
89
|
+
setup:
|
|
90
|
+
main_menu:
|
|
91
|
+
title: "Menu Principal"
|
|
92
|
+
choose_action: "Choisissez une action"
|
|
93
|
+
# ... continue translating all keys
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
3. **Translate all strings** while keeping:
|
|
97
|
+
- The YAML structure unchanged
|
|
98
|
+
- All interpolation variables like `%{action}` in their original form
|
|
99
|
+
- Special formatting like keyboard shortcuts in their original form (e.g., `(q)`)
|
|
100
|
+
|
|
101
|
+
4. **Test your translations** by setting the appropriate locale:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
LANG=fr_FR.UTF-8 imap-backup setup
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
5. **Submit a pull request** with your new translation file.
|
|
108
|
+
|
|
109
|
+
### Updating Existing Translations
|
|
110
|
+
|
|
111
|
+
When updating translations:
|
|
112
|
+
|
|
113
|
+
1. Compare your language file with `en.yml` to identify missing or changed keys
|
|
114
|
+
2. Add or update the necessary translations
|
|
115
|
+
3. Ensure the structure matches the current `en.yml` structure
|
|
116
|
+
4. Test with your locale settings
|
|
117
|
+
5. Submit a pull request
|
|
118
|
+
|
|
119
|
+
## Translation Guidelines
|
|
120
|
+
|
|
121
|
+
- **Maintain context**: Keep in mind where text appears (menus, prompts, error messages)
|
|
122
|
+
- **Preserve formatting**: Keep special characters like `(q)` for keyboard shortcuts
|
|
123
|
+
- **Keep variables**: Don't translate interpolation variables like `%{action}`
|
|
124
|
+
- **Test thoroughly**: Run the setup interface to ensure translations fit properly
|
|
125
|
+
- **Be consistent**: Use consistent terminology throughout the translation
|
|
126
|
+
- **Follow conventions**: Respect language-specific conventions for punctuation and spacing
|
|
127
|
+
|
|
128
|
+
## Current Language Support
|
|
129
|
+
|
|
130
|
+
- **English (en)**: Complete (reference implementation)
|
|
131
|
+
- **Italian (it)**: Complete
|
|
132
|
+
|
|
133
|
+
## Testing Translations
|
|
134
|
+
|
|
135
|
+
To test translations in your local environment:
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
# Test with a specific locale
|
|
139
|
+
LANG=it_IT.UTF-8 imap-backup setup
|
|
140
|
+
|
|
141
|
+
# Test with fallback to English
|
|
142
|
+
LANG=C imap-backup setup
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Need Help?
|
|
146
|
+
|
|
147
|
+
If you have questions about contributing translations:
|
|
148
|
+
|
|
149
|
+
- Check existing translation files (`en.yml` and `it.yml`) for examples
|
|
150
|
+
- Open an issue on GitHub to discuss translation conventions
|
|
151
|
+
- Ask for clarification on ambiguous strings
|
|
152
|
+
|
|
153
|
+
Thank you for helping make imap-backup accessible to users worldwide!
|
data/imap-backup.gemspec
CHANGED
|
@@ -13,6 +13,7 @@ Gem::Specification.new do |gem|
|
|
|
13
13
|
gem.files = %w[bin/imap-backup]
|
|
14
14
|
gem.files += Dir.glob("docs/*.md")
|
|
15
15
|
gem.files += Dir.glob("lib/**/*.rb")
|
|
16
|
+
gem.files += Dir.glob("lib/imap/backup/locales/*.yml")
|
|
16
17
|
gem.files += %w[imap-backup.gemspec]
|
|
17
18
|
gem.files += %w[LICENSE README.md]
|
|
18
19
|
|
|
@@ -21,6 +22,8 @@ Gem::Specification.new do |gem|
|
|
|
21
22
|
gem.required_ruby_version = ">= 3.2"
|
|
22
23
|
|
|
23
24
|
gem.add_dependency "highline"
|
|
25
|
+
gem.add_dependency "i18n"
|
|
26
|
+
gem.add_dependency "locale"
|
|
24
27
|
gem.add_dependency "logger"
|
|
25
28
|
gem.add_dependency "mail", "2.7.1"
|
|
26
29
|
gem.add_dependency "net-imap", ">= 0.3.2"
|
|
@@ -72,9 +72,7 @@ module Imap::Backup
|
|
|
72
72
|
erb_config_path = options[:erb_configuration]
|
|
73
73
|
|
|
74
74
|
# Check mutual exclusivity
|
|
75
|
-
if config_path && erb_config_path
|
|
76
|
-
raise "Cannot specify both --config and --erb-configuration options"
|
|
77
|
-
end
|
|
75
|
+
raise I18n.t("cli.helpers.config_mutual_exclusivity") if config_path && erb_config_path
|
|
78
76
|
|
|
79
77
|
# Handle ERB configuration
|
|
80
78
|
return load_erb_config(erb_config_path, options) if erb_config_path
|
|
@@ -86,7 +84,7 @@ module Imap::Backup
|
|
|
86
84
|
exists = Configuration.exist?(path: path)
|
|
87
85
|
if !exists
|
|
88
86
|
expected = path || Configuration.default_pathname
|
|
89
|
-
raise ConfigurationNotFound, "
|
|
87
|
+
raise ConfigurationNotFound, I18n.t("cli.helpers.config_not_found", path: expected)
|
|
90
88
|
end
|
|
91
89
|
end
|
|
92
90
|
Configuration.new(path: path)
|
|
@@ -96,7 +94,7 @@ module Imap::Backup
|
|
|
96
94
|
# @return [Account] the Account information for the email address
|
|
97
95
|
def account(config, email)
|
|
98
96
|
account = config.accounts.find { |a| a.username == email }
|
|
99
|
-
raise "
|
|
97
|
+
raise I18n.t("cli.helpers.account_not_configured", email: email) if !account
|
|
100
98
|
|
|
101
99
|
account
|
|
102
100
|
end
|
|
@@ -123,7 +121,7 @@ module Imap::Backup
|
|
|
123
121
|
def load_erb_config(erb_path, _options)
|
|
124
122
|
# Check if file exists
|
|
125
123
|
unless File.exist?(erb_path)
|
|
126
|
-
raise ConfigurationNotFound, "
|
|
124
|
+
raise ConfigurationNotFound, I18n.t("cli.helpers.erb_config_not_found", path: erb_path)
|
|
127
125
|
end
|
|
128
126
|
|
|
129
127
|
begin
|
|
@@ -132,16 +130,16 @@ module Imap::Backup
|
|
|
132
130
|
erb = ERB.new(erb_content)
|
|
133
131
|
rendered_json = erb.result
|
|
134
132
|
rescue SyntaxError => e
|
|
135
|
-
raise "
|
|
133
|
+
raise I18n.t("cli.helpers.erb_syntax_error", message: e.message)
|
|
136
134
|
rescue StandardError => e
|
|
137
|
-
raise "
|
|
135
|
+
raise I18n.t("cli.helpers.erb_processing_error", message: e.message)
|
|
138
136
|
end
|
|
139
137
|
|
|
140
138
|
# Validate rendered JSON
|
|
141
139
|
begin
|
|
142
140
|
JSON.parse(rendered_json)
|
|
143
141
|
rescue JSON::ParserError => e
|
|
144
|
-
raise "
|
|
142
|
+
raise I18n.t("cli.helpers.erb_invalid_json", message: e.message)
|
|
145
143
|
end
|
|
146
144
|
|
|
147
145
|
# Create temporary file with rendered JSON
|
|
@@ -64,7 +64,7 @@ module Imap::Backup
|
|
|
64
64
|
|
|
65
65
|
def print_check_results_as_text(results)
|
|
66
66
|
results.each do |account_results|
|
|
67
|
-
Kernel.puts "
|
|
67
|
+
Kernel.puts I18n.t("cli.local.check.account", account: account_results[:account])
|
|
68
68
|
account_results[:folders].each do |folder_results|
|
|
69
69
|
Kernel.puts "\t#{folder_results[:name]}: #{folder_results[:result]}"
|
|
70
70
|
end
|
|
@@ -95,7 +95,7 @@ module Imap::Backup
|
|
|
95
95
|
serializer, _folder = serialized_folders.find do |_s, f|
|
|
96
96
|
f.name == folder_name
|
|
97
97
|
end
|
|
98
|
-
raise "
|
|
98
|
+
raise I18n.t("cli.local.folder_not_found", folder: folder_name) if !serializer
|
|
99
99
|
|
|
100
100
|
case options[:format]
|
|
101
101
|
when "json"
|
|
@@ -126,7 +126,7 @@ module Imap::Backup
|
|
|
126
126
|
serializer, _folder = serialized_folders.find do |_s, f|
|
|
127
127
|
f.name == folder_name
|
|
128
128
|
end
|
|
129
|
-
raise "
|
|
129
|
+
raise I18n.t("cli.local.folder_not_found", folder: folder_name) if !serializer
|
|
130
130
|
|
|
131
131
|
uid_list = uids.split(",")
|
|
132
132
|
|
|
@@ -115,7 +115,9 @@ module Imap::Backup
|
|
|
115
115
|
def list_namespaces(namespaces)
|
|
116
116
|
Kernel.puts format(
|
|
117
117
|
NAMESPACE_TEMPLATE,
|
|
118
|
-
{name: "
|
|
118
|
+
{name: I18n.t("cli.remote.namespaces.name"),
|
|
119
|
+
prefix: I18n.t("cli.remote.namespaces.prefix"),
|
|
120
|
+
delim: I18n.t("cli.remote.namespaces.delimiter")}
|
|
119
121
|
)
|
|
120
122
|
list_namespace namespaces, :personal
|
|
121
123
|
list_namespace namespaces, :other
|
|
@@ -127,7 +129,8 @@ module Imap::Backup
|
|
|
127
129
|
if info
|
|
128
130
|
Kernel.puts format(NAMESPACE_TEMPLATE, name: name, **info)
|
|
129
131
|
else
|
|
130
|
-
Kernel.puts format("%-10<name>s
|
|
132
|
+
Kernel.puts format("%-10<name>s %<not_defined>s",
|
|
133
|
+
name: name, not_defined: I18n.t("cli.remote.namespaces.not_defined"))
|
|
131
134
|
end
|
|
132
135
|
end
|
|
133
136
|
|
|
@@ -38,7 +38,7 @@ module Imap::Backup
|
|
|
38
38
|
account = account(config, email)
|
|
39
39
|
restore(account, **restore_options)
|
|
40
40
|
when email && options.key?(:accounts)
|
|
41
|
-
raise "
|
|
41
|
+
raise I18n.t("cli.restore.missing_email_parameter")
|
|
42
42
|
when !email && options.key?(:accounts)
|
|
43
43
|
Logger.logger.info(
|
|
44
44
|
"Calling restore with the --account option is deprecated, " \
|
|
@@ -42,10 +42,10 @@ module Imap::Backup
|
|
|
42
42
|
private
|
|
43
43
|
|
|
44
44
|
TEXT_COLUMNS = [
|
|
45
|
-
{name: :folder, width: 20, alignment: :left},
|
|
46
|
-
{name: :remote, width: 8, alignment: :right},
|
|
47
|
-
{name: :both, width: 8, alignment: :right},
|
|
48
|
-
{name: :local, width: 8, alignment: :right}
|
|
45
|
+
{name: :folder, i18n_key: "cli.stats.folder", width: 20, alignment: :left},
|
|
46
|
+
{name: :remote, i18n_key: "cli.stats.remote", width: 8, alignment: :right},
|
|
47
|
+
{name: :both, i18n_key: "cli.stats.both", width: 8, alignment: :right},
|
|
48
|
+
{name: :local, i18n_key: "cli.stats.local", width: 8, alignment: :right}
|
|
49
49
|
].freeze
|
|
50
50
|
ALIGNMENT_FORMAT_SYMBOL = {left: "-", right: " "}.freeze
|
|
51
51
|
private_constant :TEXT_COLUMNS, :ALIGNMENT_FORMAT_SYMBOL
|
|
@@ -96,7 +96,7 @@ module Imap::Backup
|
|
|
96
96
|
|
|
97
97
|
def text_header
|
|
98
98
|
titles = TEXT_COLUMNS.map do |column|
|
|
99
|
-
format("%-#{column[:width]}s", column[:
|
|
99
|
+
format("%-#{column[:width]}s", I18n.t(column[:i18n_key]))
|
|
100
100
|
end.join("|")
|
|
101
101
|
|
|
102
102
|
underline = TEXT_COLUMNS.map do |column|
|
|
@@ -97,23 +97,24 @@ module Imap::Backup
|
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
def check_accounts!
|
|
100
|
-
if destination_email == source_email
|
|
101
|
-
raise "Source and destination accounts cannot be the same!"
|
|
102
|
-
end
|
|
100
|
+
raise I18n.t("cli.transfer.same_account_error") if destination_email == source_email
|
|
103
101
|
|
|
104
|
-
|
|
102
|
+
if !destination_account
|
|
103
|
+
raise I18n.t("cli.transfer.destination_not_found",
|
|
104
|
+
email: destination_email)
|
|
105
|
+
end
|
|
105
106
|
|
|
106
|
-
raise "
|
|
107
|
+
raise I18n.t("cli.transfer.source_not_found", email: source_email) if !source_account
|
|
107
108
|
|
|
108
109
|
if !source_account.available_for_migration?
|
|
109
|
-
raise "
|
|
110
|
-
|
|
110
|
+
raise I18n.t("cli.transfer.source_not_available",
|
|
111
|
+
email: source_email, status: source_account.status)
|
|
111
112
|
end
|
|
112
113
|
|
|
113
114
|
return if destination_account.available_for_migration?
|
|
114
115
|
|
|
115
|
-
raise "
|
|
116
|
-
|
|
116
|
+
raise I18n.t("cli.transfer.destination_not_available",
|
|
117
|
+
email: destination_email, status: destination_account.status)
|
|
117
118
|
end
|
|
118
119
|
|
|
119
120
|
def choose_prefixes_and_delimiters!
|
|
@@ -126,12 +127,10 @@ module Imap::Backup
|
|
|
126
127
|
end
|
|
127
128
|
|
|
128
129
|
def ensure_no_prefix_or_delimiter_parameters!
|
|
129
|
-
if destination_delimiter
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
raise "
|
|
133
|
-
raise "--automatic-namespaces is incompatible with --source-delimiter" if source_delimiter
|
|
134
|
-
raise "--automatic-namespaces is incompatible with --source-prefix" if source_prefix
|
|
130
|
+
raise I18n.t("cli.transfer.incompatible_destination_delimiter") if destination_delimiter
|
|
131
|
+
raise I18n.t("cli.transfer.incompatible_destination_prefix") if destination_prefix
|
|
132
|
+
raise I18n.t("cli.transfer.incompatible_source_delimiter") if source_delimiter
|
|
133
|
+
raise I18n.t("cli.transfer.incompatible_source_prefix") if source_prefix
|
|
135
134
|
end
|
|
136
135
|
|
|
137
136
|
def query_servers_for_settings
|
|
@@ -76,14 +76,17 @@ module Imap::Backup
|
|
|
76
76
|
profile = thunderbird_profile(profile_name)
|
|
77
77
|
|
|
78
78
|
if !profile
|
|
79
|
-
|
|
79
|
+
if profile_name
|
|
80
|
+
raise I18n.t("cli.utils.thunderbird_profile_not_found",
|
|
81
|
+
profile: profile_name)
|
|
82
|
+
end
|
|
80
83
|
|
|
81
|
-
raise "
|
|
84
|
+
raise I18n.t("cli.utils.default_thunderbird_profile_not_found")
|
|
82
85
|
end
|
|
83
86
|
|
|
84
87
|
serialized_folders = Account::SerializedFolders.new(account: account)
|
|
85
88
|
|
|
86
|
-
raise "
|
|
89
|
+
raise I18n.t("cli.utils.no_serialized_folders", email: email) if serialized_folders.none?
|
|
87
90
|
|
|
88
91
|
serialized_folders.each_key do |serializer|
|
|
89
92
|
Thunderbird::MailboxExporter.new(
|
data/lib/imap/backup/cli.rb
CHANGED
|
@@ -16,10 +16,7 @@ module Imap::Backup
|
|
|
16
16
|
# The default download strategy key
|
|
17
17
|
DEFAULT_STRATEGY = "delay_metadata".freeze
|
|
18
18
|
# The available download strategies
|
|
19
|
-
DOWNLOAD_STRATEGIES = [
|
|
20
|
-
{key: "direct", description: "write straight to disk"},
|
|
21
|
-
{key: DEFAULT_STRATEGY, description: "delay writing metadata"}
|
|
22
|
-
].freeze
|
|
19
|
+
DOWNLOAD_STRATEGIES = ["direct", DEFAULT_STRATEGY].freeze
|
|
23
20
|
# The current file version
|
|
24
21
|
VERSION = "2.2".freeze
|
|
25
22
|
|
|
@@ -77,7 +74,7 @@ module Imap::Backup
|
|
|
77
74
|
end
|
|
78
75
|
end
|
|
79
76
|
|
|
80
|
-
# @return [String] the
|
|
77
|
+
# @return [String] the configured download strategy
|
|
81
78
|
def download_strategy
|
|
82
79
|
ensure_loaded!
|
|
83
80
|
|
|
@@ -87,7 +84,7 @@ module Imap::Backup
|
|
|
87
84
|
# @param value [String] the new strategy
|
|
88
85
|
# @return [void]
|
|
89
86
|
def download_strategy=(value)
|
|
90
|
-
raise "Unknown strategy '#{value}'" if !DOWNLOAD_STRATEGIES.
|
|
87
|
+
raise "Unknown strategy '#{value}'" if !DOWNLOAD_STRATEGIES.include?(value)
|
|
91
88
|
|
|
92
89
|
ensure_loaded!
|
|
93
90
|
|
|
@@ -133,7 +130,7 @@ module Imap::Backup
|
|
|
133
130
|
contents = File.read(pathname)
|
|
134
131
|
data = JSON.parse(contents, symbolize_names: true)
|
|
135
132
|
data[:download_strategy] =
|
|
136
|
-
if DOWNLOAD_STRATEGIES.
|
|
133
|
+
if DOWNLOAD_STRATEGIES.include?(data[:download_strategy])
|
|
137
134
|
data[:download_strategy]
|
|
138
135
|
else
|
|
139
136
|
DEFAULT_STRATEGY
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
en:
|
|
3
|
+
setup:
|
|
4
|
+
main_menu:
|
|
5
|
+
title: "Main Menu"
|
|
6
|
+
choose_action: "Choose an action"
|
|
7
|
+
add_account: "add account"
|
|
8
|
+
modify_global_options: "modify global options"
|
|
9
|
+
save_and_exit: "save and exit"
|
|
10
|
+
exit_without_saving: "exit without saving changes"
|
|
11
|
+
quit: "(q) quit"
|
|
12
|
+
account:
|
|
13
|
+
title: "Account"
|
|
14
|
+
choose_action: "Choose an action"
|
|
15
|
+
modify_email: "modify email"
|
|
16
|
+
modify_password: "modify password"
|
|
17
|
+
modify_backup_path: "modify backup path"
|
|
18
|
+
toggle_folder_blacklist: "toggle folder inclusion mode (whitelist/blacklist)"
|
|
19
|
+
choose_folders: "choose folders to %{action}"
|
|
20
|
+
exclude_from_backups: "exclude from backups"
|
|
21
|
+
include_in_backups: "include in backups"
|
|
22
|
+
toggle_mirror_mode: "toggle mode ('keep'/'mirror')"
|
|
23
|
+
modify_multi_fetch_size: "modify multi-fetch size (number of emails to fetch at a time)"
|
|
24
|
+
size_prompt: "size: "
|
|
25
|
+
modify_server: "modify server"
|
|
26
|
+
server_prompt: "server: "
|
|
27
|
+
modify_connection_options: "modify connection options"
|
|
28
|
+
connection_options_prompt: "connection options (as JSON): "
|
|
29
|
+
malformed_json: "Malformed JSON, please try again"
|
|
30
|
+
press_key: "Press a key "
|
|
31
|
+
dont_fix_unread_flags: "don't fix changes to 'UNSEEN' flags during download"
|
|
32
|
+
fix_unread_flags: "fix changes to 'UNSEEN' flags during download"
|
|
33
|
+
change_status: "change status (%{current} -> %{next})"
|
|
34
|
+
test_connection: "test connection"
|
|
35
|
+
delete: "delete"
|
|
36
|
+
delete_confirm: "Are you sure? (y/n) "
|
|
37
|
+
return_to_main_menu: "(q) return to main menu"
|
|
38
|
+
email: "email"
|
|
39
|
+
password: "password"
|
|
40
|
+
unset: "(unset)"
|
|
41
|
+
path: "path"
|
|
42
|
+
exclude: "exclude"
|
|
43
|
+
include: "include"
|
|
44
|
+
all_folders: "(all folders)"
|
|
45
|
+
no_folders_warning: "(all folders) <- you have opted to not backup any folders!"
|
|
46
|
+
mirror_emails: "mirror emails"
|
|
47
|
+
keep_all_emails: "keep all emails"
|
|
48
|
+
mode: "mode"
|
|
49
|
+
multi_fetch: "multi-fetch"
|
|
50
|
+
server: "server"
|
|
51
|
+
connection_options: "connection options"
|
|
52
|
+
reset_seen_flags_message: "changes to 'UNSEEN' flags will be reset during download"
|
|
53
|
+
status: "status"
|
|
54
|
+
global_options:
|
|
55
|
+
title: "Global Options"
|
|
56
|
+
description: "These settings affect all accounts."
|
|
57
|
+
choose_action: "Choose an action"
|
|
58
|
+
change_download_strategy: "change download strategy (currently: '%{current}')"
|
|
59
|
+
return_to_main_menu: "(q) return to main menu"
|
|
60
|
+
download_strategy_chooser:
|
|
61
|
+
title: "Choose a Download Strategy"
|
|
62
|
+
current_marker: " <- current"
|
|
63
|
+
help: "help"
|
|
64
|
+
help_text: |
|
|
65
|
+
This setting changes how often data is written to disk during backups.
|
|
66
|
+
|
|
67
|
+
imap-backup uses two files per folder, a .mbox file with the actual
|
|
68
|
+
messages and a .imap file with metadata like message lengths and their
|
|
69
|
+
offsets within the .mbox file.
|
|
70
|
+
|
|
71
|
+
# write straight to disk
|
|
72
|
+
|
|
73
|
+
With this setting, each message and its metadata are written to disk
|
|
74
|
+
as they are downloaded.
|
|
75
|
+
|
|
76
|
+
This choice uses least memory and so is suitable for backing up onto
|
|
77
|
+
devices with limited memory, like Raspberry Pis.
|
|
78
|
+
|
|
79
|
+
# delay writing metadata
|
|
80
|
+
|
|
81
|
+
This is the default setting.
|
|
82
|
+
|
|
83
|
+
Here, messages (which are potentially very large) are appended to the
|
|
84
|
+
.mbox file as they are received, but the metadata is only written to
|
|
85
|
+
the .imap file once all the folder's messages have been downloaded.
|
|
86
|
+
|
|
87
|
+
This choice uses a little more memory than the previous setting, but
|
|
88
|
+
is **much** faster for large folders (potentially >30 times for
|
|
89
|
+
folders with >100k messages) and is less wearing on the disk.
|
|
90
|
+
|
|
91
|
+
# Other Performance Settings
|
|
92
|
+
|
|
93
|
+
Another configuration which affects backup performance is the
|
|
94
|
+
`multi_fetch_size` account-level setting.
|
|
95
|
+
press_key: "Press a key "
|
|
96
|
+
return_to_main_menu: "(q) return to main menu"
|
|
97
|
+
asker:
|
|
98
|
+
email_prompt: "email address: "
|
|
99
|
+
email_validation_error: "Enter a valid email address "
|
|
100
|
+
password_prompt: "password: "
|
|
101
|
+
password_confirmation_prompt: "repeat password: "
|
|
102
|
+
password_mismatch: "the password and confirmation did not match.\nContinue? (y/n) "
|
|
103
|
+
folder_chooser:
|
|
104
|
+
title: "Add/remove folders"
|
|
105
|
+
select_folder: "Select a folder (toggles)"
|
|
106
|
+
return_to_account_menu: "(q) return to the account menu"
|
|
107
|
+
press_key: "Press a key "
|
|
108
|
+
unable_to_get_folder_list: "Unable to get folder list"
|
|
109
|
+
folders_removed: "The following folders have been removed: %{folders}"
|
|
110
|
+
connection_failed: "Connection failed"
|
|
111
|
+
backup_path:
|
|
112
|
+
prompt: "backup directory: "
|
|
113
|
+
validation_error: "Choose a different directory "
|
|
114
|
+
path_in_use: "The path '%{path}' is used to backup the account '%{username}'"
|
|
115
|
+
cli:
|
|
116
|
+
version: "imap-backup %{version}"
|
|
117
|
+
stats:
|
|
118
|
+
folder: "folder"
|
|
119
|
+
remote: "remote"
|
|
120
|
+
both: "both"
|
|
121
|
+
local: "local"
|
|
122
|
+
local:
|
|
123
|
+
check:
|
|
124
|
+
account: "Account: %{account}"
|
|
125
|
+
remote:
|
|
126
|
+
namespaces:
|
|
127
|
+
name: "Name"
|
|
128
|
+
prefix: "Prefix"
|
|
129
|
+
delimiter: "Delimiter"
|
|
130
|
+
not_defined: "(Not defined)"
|
|
131
|
+
restore:
|
|
132
|
+
missing_email_parameter: "Missing EMAIL parameter"
|
|
133
|
+
transfer:
|
|
134
|
+
same_account_error: "Source and destination accounts cannot be the same!"
|
|
135
|
+
destination_not_found: "Account '%{email}' does not exist"
|
|
136
|
+
source_not_found: "Account '%{email}' does not exist"
|
|
137
|
+
source_not_available: "Account '%{email}' is not available for migration (status: %{status})"
|
|
138
|
+
destination_not_available: "Account '%{email}' is not available for migration (status: %{status})"
|
|
139
|
+
incompatible_destination_delimiter: "--automatic-namespaces is incompatible with --destination-delimiter"
|
|
140
|
+
incompatible_destination_prefix: "--automatic-namespaces is incompatible with --destination-prefix"
|
|
141
|
+
incompatible_source_delimiter: "--automatic-namespaces is incompatible with --source-delimiter"
|
|
142
|
+
incompatible_source_prefix: "--automatic-namespaces is incompatible with --source-prefix"
|
|
143
|
+
helpers:
|
|
144
|
+
config_mutual_exclusivity: "Cannot specify both --config and --erb-configuration options"
|
|
145
|
+
config_not_found: "Configuration file '%{path}' not found"
|
|
146
|
+
account_not_configured: "%{email} is not a configured account"
|
|
147
|
+
erb_config_not_found: "ERB configuration file '%{path}' not found"
|
|
148
|
+
erb_syntax_error: "ERB template has syntax error: %{message}"
|
|
149
|
+
erb_processing_error: "Error processing ERB template: %{message}"
|
|
150
|
+
erb_invalid_json: "ERB template rendered invalid JSON: %{message}"
|
|
151
|
+
local:
|
|
152
|
+
check:
|
|
153
|
+
account: "Account: %{account}"
|
|
154
|
+
folder_not_found: "Folder '%{folder}' not found"
|
|
155
|
+
utils:
|
|
156
|
+
thunderbird_profile_not_found: "Thunderbird profile '%{profile}' not found"
|
|
157
|
+
default_thunderbird_profile_not_found: "Default Thunderbird profile not found"
|
|
158
|
+
no_serialized_folders: "No serialized folders were found for account '%{email}'"
|
|
159
|
+
configuration:
|
|
160
|
+
download_strategy:
|
|
161
|
+
direct:
|
|
162
|
+
short: "write straight to disk"
|
|
163
|
+
long: "write to disk as messages are downloaded"
|
|
164
|
+
delay_metadata:
|
|
165
|
+
short: "delay writing metadata"
|
|
166
|
+
long: "delay writing metadata until all messages are downloaded"
|