Skip to content

Commit d02b40d

Browse files
committed
Add option to disable weekly updates from Crowdin
T`{"weekly_update_from_crowdin": false}` in `factorio-mods-localization.json`
1 parent 19fe2cc commit d02b40d

File tree

6 files changed

+170
-22
lines changed

6 files changed

+170
-22
lines changed

README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ We provide service for simplifying [Factorio](https://www.factorio.com/) mods tr
1414
* Every week our [FactorioBot](https://github.com/factorio-mods-helper) will fetch translation updates from Crowdin and commit them to your repository
1515

1616
## Motivation
17-
There are a lot of Factorio mods hosted on GitHub. Most of them are translated using pull requests. I think it is not very convenient (because it is unclear which strings are untranslated yet and translators have to know how to use git). So, I created a helper tool for configuring the translation process on Crowdin, a powerful localization platform.
17+
There are a lot of Factorio mods hosted on GitHub. Most of them are translated using pull requests. It is not very convenient (because it is unclear which strings are untranslated yet and translators have to know how to use git). So, I created a helper tool for configuring the translation process on Crowdin, a powerful localization platform.
1818

1919
## Installation
2020
1. Go to our [GitHub app page][1]
@@ -40,19 +40,29 @@ Then Crowdin translation interface will be opened where you can translate string
4040
## Notes
4141

4242
* To correctly upload your existing translations to Crowdin, files in any localization folder (such as `/locale/de`) **must have the same names as files in `/locale/en` folder**.
43-
* If a repository has branch protection rules, then our helper will create a pull request (instead of pushing to the main branch directly).
43+
* If a repository has branch protection rules, our helper will create a pull request (instead of pushing to the main branch directly).
4444
* Please ask any questions or report bugs by creating a new [issue](https://github.com/dima74/factorio-mods-localization/issues).
4545

4646
## Multimods
47-
It is possible to have multiple Factorio mods in a single GitHub repository. In this case please add file `factorio-mods-localization.json` with the list of mods to the root of the repository. See [`Omnimods/Omnimods` repository](https://github.com/Omnimods/Omnimods/commit/1e689afcf202776ffa0f675f73353f1fd67d2039) as an example.
47+
It is possible to have multiple Factorio mods in a single GitHub repository. In this case please add file `factorio-mods-localization.json` with the list of mods to the root of the repository:
4848
```
49-
├── factorio-mods-localization.json // ["Mod1", "Mod2"]
49+
├── factorio-mods-localization.json // {"mods": ["Mod1", "Mod2"]}
5050
├── Mod1
5151
│ ├── locale/en
5252
├── Mod2
5353
│ ├── locale/en
5454
```
5555

56+
## Disable weekly updates from Crowdin
57+
It is possible to disable automatic weekly updates from Crowdin and perform updates manually when you need it. To do so, create file `factorio-mods-localization.json` in the root of your repository with content:
58+
```json
59+
{"weekly_update_from_crowdin": false}
60+
```
61+
Now you can perform an update manually using the following URL:
62+
```
63+
https://factorio-mods-localization.fly.dev/api/triggerUpdate?repo=OWNER/REPO
64+
```
65+
5666
## Detailed description of how it works
5767
0. Mod author has a mod repository on GitHub
5868
1. Mod author installs GitHub app (for mod repository)

examples/on_repository_added.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ use fml::github_mod_name::GithubModName;
55
async fn main() {
66
fml::init_with_crowdin().await;
77
let installation_id = github::get_installation_id_for_user("dima74").await;
8-
let mod_ = GithubModName::new("dima74/factorio-mod-example", None);
8+
let mod_ = GithubModName::new("dima74/factorio-mod-example", None, None);
99
webhooks::on_repository_added("dima74/factorio-mod-example", vec![mod_], installation_id).await;
1010
}

src/github.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ pub async fn extract_mods_from_repository(installation_api: &Octocrab, full_name
7979
let locale_en_items = list_files_in_directory(installation_api, full_name, "locale/en").await;
8080
match locale_en_items {
8181
Ok(locale_en_items) if !locale_en_items.is_empty() => {
82-
vec![GithubModName::new(full_name, None)]
82+
vec![GithubModName::new(full_name, None, None)]
8383
}
8484
_ => vec![],
8585
}
@@ -309,13 +309,13 @@ mod tests {
309309
let api = as_installation_for_user("dima74").await;
310310
assert_eq!(
311311
extract_mods_from_repository(&api, "dima74/factorio-mod-example").await,
312-
vec![GithubModName::new("dima74/factorio-mod-example", None)],
312+
vec![GithubModName::new("dima74/factorio-mod-example", None, None)],
313313
);
314314
assert_eq!(
315315
extract_mods_from_repository(&api, "dima74/factorio-multimod-example").await,
316316
vec![
317-
GithubModName::new("dima74/factorio-multimod-example", Some("Mod1".to_owned())),
318-
GithubModName::new("dima74/factorio-multimod-example", Some("Mod2".to_owned())),
317+
GithubModName::new("dima74/factorio-multimod-example", Some("Mod1".to_owned()), None),
318+
GithubModName::new("dima74/factorio-multimod-example", Some("Mod2".to_owned()), None),
319319
],
320320
);
321321
assert_eq!(

src/github_mod_name.rs

Lines changed: 133 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use std::fmt;
22

3+
use serde::Deserialize;
4+
35
/// # Single mod in github repository
46
/// .
57
/// ├── locale/en
68
///
79
/// # Multiple mods in github repository
810
/// .
9-
/// ├── factorio-mods-localization.json // ["Mod1", "Mod2"]
11+
/// ├── factorio-mods-localization.json // {"mods": ["Mod1", "Mod2"]}
1012
/// ├── Mod1
1113
/// │ ├── locale/en
1214
/// ├── Mod2
@@ -17,6 +19,9 @@ pub struct GithubModName {
1719
pub owner: String,
1820
pub repo: String,
1921
pub subpath: Option<String>,
22+
// This is not good design of data structure. Instead this should be something like this:
23+
// struct GithubRepoInfo { mods: Vec<...>, weekly_update_from_crowdin: bool }
24+
pub weekly_update_from_crowdin: bool,
2025
}
2126

2227
impl fmt::Display for GithubModName {
@@ -29,24 +34,141 @@ impl fmt::Display for GithubModName {
2934
}
3035

3136
impl GithubModName {
32-
pub fn new(full_name: &str, subpath: Option<String>) -> Self {
37+
pub fn new(full_name: &str, subpath: Option<String>, weekly_update_from_crowdin: Option<bool>) -> Self {
3338
let (owner, repo) = full_name.split_once('/').unwrap();
3439
Self {
3540
owner: owner.to_owned(),
3641
repo: repo.to_owned(),
3742
subpath,
43+
weekly_update_from_crowdin: weekly_update_from_crowdin.unwrap_or(true),
3844
}
3945
}
4046
}
4147

48+
/// # Format of factorio-mods-localization.json
49+
/// Old format:
50+
/// ```json
51+
/// ["mod1", "mod2"]
52+
/// ```
53+
///
54+
/// New format:
55+
/// ```json
56+
/// {
57+
/// "mods": ["mod1", "mod2"],
58+
/// "weekly_update_from_crowdin": false
59+
/// }
60+
/// ```
4261
pub fn parse_github_mod_names_json(full_name: &str, json: &str) -> Vec<GithubModName> {
43-
let names: Vec<String> = serde_json::from_str(&json).unwrap();
44-
names
45-
.into_iter()
46-
.map(|name| {
47-
// only direct subdirectories are supported
48-
assert!(name != "" && !name.starts_with(".") && !name.contains('/'));
49-
GithubModName::new(full_name, Some(name))
50-
})
51-
.collect()
62+
#[derive(Deserialize)]
63+
struct Data {
64+
mods: Option<Vec<String>>,
65+
weekly_update_from_crowdin: Option<bool>,
66+
}
67+
let (mods, weekly_update_from_crowdin) = match serde_json::from_str::<Data>(&json) {
68+
Ok(data) => {
69+
(data.mods, data.weekly_update_from_crowdin)
70+
}
71+
Err(_) => {
72+
let mods: Vec<String> = serde_json::from_str(&json).unwrap();
73+
(Some(mods), None)
74+
}
75+
};
76+
match mods {
77+
None => {
78+
// { "weekly_update_from_crowdin": false }
79+
vec![GithubModName::new(full_name, None, weekly_update_from_crowdin)]
80+
}
81+
Some(mods) => {
82+
mods
83+
.into_iter()
84+
.map(|name| {
85+
// only direct subdirectories are supported
86+
assert!(name != "" && !name.starts_with(".") && !name.contains('/'));
87+
GithubModName::new(full_name, Some(name), weekly_update_from_crowdin)
88+
})
89+
.collect()
90+
}
91+
}
92+
}
93+
94+
#[cfg(test)]
95+
mod tests {
96+
use super::*;
97+
98+
#[test]
99+
fn test_parse_github_mod_names_json() {
100+
assert_eq!(
101+
parse_github_mod_names_json("owner/repo", r#"["mod1", "mod2"]"#),
102+
vec![
103+
GithubModName {
104+
owner: "owner".to_owned(),
105+
repo: "repo".to_owned(),
106+
subpath: Some("mod1".to_owned()),
107+
weekly_update_from_crowdin: true
108+
},
109+
GithubModName {
110+
owner: "owner".to_owned(),
111+
repo: "repo".to_owned(),
112+
subpath: Some("mod2".to_owned()),
113+
weekly_update_from_crowdin: true
114+
},
115+
]
116+
);
117+
assert_eq!(
118+
parse_github_mod_names_json("owner/repo", r#"{"mods": ["mod1", "mod2"]}"#),
119+
vec![
120+
GithubModName {
121+
owner: "owner".to_owned(),
122+
repo: "repo".to_owned(),
123+
subpath: Some("mod1".to_owned()),
124+
weekly_update_from_crowdin: true
125+
},
126+
GithubModName {
127+
owner: "owner".to_owned(),
128+
repo: "repo".to_owned(),
129+
subpath: Some("mod2".to_owned()),
130+
weekly_update_from_crowdin: true
131+
},
132+
]
133+
);
134+
assert_eq!(
135+
parse_github_mod_names_json("owner/repo", r#"{"mods": ["mod1", "mod2"], "weekly_update_from_crowdin": false}"#),
136+
vec![
137+
GithubModName {
138+
owner: "owner".to_owned(),
139+
repo: "repo".to_owned(),
140+
subpath: Some("mod1".to_owned()),
141+
weekly_update_from_crowdin: false
142+
},
143+
GithubModName {
144+
owner: "owner".to_owned(),
145+
repo: "repo".to_owned(),
146+
subpath: Some("mod2".to_owned()),
147+
weekly_update_from_crowdin: false
148+
},
149+
]
150+
);
151+
assert_eq!(
152+
parse_github_mod_names_json("owner/repo", r#"{"weekly_update_from_crowdin": false}"#),
153+
vec![
154+
GithubModName {
155+
owner: "owner".to_owned(),
156+
repo: "repo".to_owned(),
157+
subpath: None,
158+
weekly_update_from_crowdin: false
159+
},
160+
]
161+
);
162+
assert_eq!(
163+
parse_github_mod_names_json("owner/repo", r#"{"weekly_update_from_crowdin": true}"#),
164+
vec![
165+
GithubModName {
166+
owner: "owner".to_owned(),
167+
repo: "repo".to_owned(),
168+
subpath: None,
169+
weekly_update_from_crowdin: true
170+
},
171+
]
172+
);
173+
}
52174
}

src/mod_directory.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub fn get_mods_impl(full_name: &str, root: &Path) -> Vec<GithubModName> {
3737
parse_github_mod_names_json(full_name, &json)
3838
} else {
3939
// Usual case - single mod at root of the github repository
40-
vec![GithubModName::new(full_name, None)]
40+
vec![GithubModName::new(full_name, None, None)]
4141
}
4242
}
4343

src/server/trigger_update.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ pub async fn get_installation_id_and_mods(
8080
};
8181

8282
let mods = if subpath.is_some() {
83-
vec![GithubModName::new(&repo, subpath)]
83+
vec![GithubModName::new(&repo, subpath, None)]
8484
} else {
8585
let api = github::as_installation(installation_id);
8686
extract_mods_from_repository(&api, &repo).await
@@ -96,10 +96,26 @@ async fn trigger_update_all_repositories() {
9696
info!("\n[update-github-from-crowdin] [*] starting...");
9797
let mut api = github::as_app();
9898
let repositories = github::get_all_repositories(&mut api).await;
99+
let repositories = filter_repositories_for_update_all(repositories);
99100
push_crowdin_changes_to_repositories(repositories).await;
100101
info!("[update-github-from-crowdin] [*] success");
101102
}
102103

104+
fn filter_repositories_for_update_all(
105+
repositories: Vec<(String, Vec<GithubModName>, InstallationId)>
106+
) -> Vec<(String, Vec<GithubModName>, InstallationId)> {
107+
repositories
108+
.into_iter()
109+
.filter(|(full_name, mods, _)| {
110+
let weekly_update_from_crowdin = mods.iter().all(|it| it.weekly_update_from_crowdin);
111+
if !weekly_update_from_crowdin {
112+
info!("[update-github-from-crowdin] [{}] skipping update because weekly_update_from_crowdin=false", full_name);
113+
}
114+
weekly_update_from_crowdin
115+
})
116+
.collect()
117+
}
118+
103119
async fn push_crowdin_changes_to_repositories(repositories: Vec<(String, Vec<GithubModName>, InstallationId)>) {
104120
let repositories = crowdin::filter_repositories(repositories).await;
105121
if repositories.is_empty() { return; }

0 commit comments

Comments
 (0)