diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3943ae5f..4c23b7d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,10 +31,6 @@ Making a new release? Simply add the new header with the version and date undern
## Unreleased
-* LocalizationTables no longer report as changed when nothing changes. [(#1333)]
-
-[#1333]: https://github.com/rojo-rbx/rojo/pull/1333
-
## [7.7.1] (October 1st, 2026)
* Fixed `$path` values that point outside the project folder failing to match `syncRule`s on Windows, which broke `rojo sourcemap` with a "could not be turned into a Roblox Instance" error. ([#1290])
@@ -42,7 +38,6 @@ Making a new release? Simply add the new header with the version and date undern
* Fixed `rojo sourcemap --absolute` emitting verbatim (`\\?\`) paths on Windows, which broke require types in luau-lsp. ([#1290])
* The plugin now disables the `Check for Updates` setting if you block access to `api.github.com`. ([#1297])
-[7.7.1]: https://github.com/rojo-rbx/rojo/releases/tag/v7.7.1
[#1290]: https://github.com/rojo-rbx/rojo/pull/1290
[#1297]: https://github.com/rojo-rbx/rojo/pull/1297
@@ -65,7 +60,6 @@ Making a new release? Simply add the new header with the version and date undern
* `rojo serve` now validates the `Host`/`Origin` headers to protect the local/private server against DNS rebinding, gates `/api/open` to local clients, and warns when bound to a network-reachable address. The accepted hosts can be extended with the `--allowed-hosts` option or a project's `serveAllowedHosts` field, for example to reach a network-exposed server by hostname. ([#1270])
* Fixed syncback not removing stale `$properties` entries when Studio resets a property to its engine default. ([#1244])
-[7.7.0]: https://github.com/rojo-rbx/rojo/releases/tag/v7.7.0
[#1176]: https://github.com/rojo-rbx/rojo/pull/1176
[#1179]: https://github.com/rojo-rbx/rojo/pull/1179
[#1192]: https://github.com/rojo-rbx/rojo/pull/1192
diff --git a/rojo-test/build-test-snapshots/end_to_end__tests__build__csv_bug_145.snap b/rojo-test/build-test-snapshots/end_to_end__tests__build__csv_bug_145.snap
index 96cdc002..ab7470b3 100644
--- a/rojo-test/build-test-snapshots/end_to_end__tests__build__csv_bug_145.snap
+++ b/rojo-test/build-test-snapshots/end_to_end__tests__build__csv_bug_145.snap
@@ -10,7 +10,7 @@ expression: contents
-
normal
- [{"example":"A number demonstrating issue 145","key":"Count","source":"3","values":{"es":"7"}}]
+ [{"key":"Count","example":"A number demonstrating issue 145","source":"3","values":{"es":"7"}}]
diff --git a/rojo-test/build-test-snapshots/end_to_end__tests__build__csv_bug_147.snap b/rojo-test/build-test-snapshots/end_to_end__tests__build__csv_bug_147.snap
index 8a668402..d16de20f 100644
--- a/rojo-test/build-test-snapshots/end_to_end__tests__build__csv_bug_147.snap
+++ b/rojo-test/build-test-snapshots/end_to_end__tests__build__csv_bug_147.snap
@@ -10,7 +10,7 @@ expression: contents
-
normal
- [{"key":"Label.Opacity","source":"Opacity","values":{}},{"key":"Label.Thickness","source":"Thickness","values":{}},{"key":"Language.Name","source":"English","values":{}},{"key":"Language.Region","source":"United States","values":{}},{"key":"Toolbar.Camera","source":"Top-down camera","values":{}},{"key":"Toolbar.Mode.Pixel","source":"Pixel mode","values":{}},{"key":"Toolbar.Mode.Vector","source":"Vector mode","values":{}},{"key":"Toolbar.Preferences","source":"Settings","values":{}},{"key":"Toolbar.Redo","source":"Redo","values":{}},{"key":"Toolbar.Saves","source":"Saved drawings","values":{}},{"key":"Toolbar.Undo","source":"Undo","values":{}}]
+ [{"key":"Language.Name","source":"English","values":{}},{"key":"Language.Region","source":"United States","values":{}},{"key":"Label.Thickness","source":"Thickness","values":{}},{"key":"Label.Opacity","source":"Opacity","values":{}},{"key":"Toolbar.Undo","source":"Undo","values":{}},{"key":"Toolbar.Redo","source":"Redo","values":{}},{"key":"Toolbar.Camera","source":"Top-down camera","values":{}},{"key":"Toolbar.Saves","source":"Saved drawings","values":{}},{"key":"Toolbar.Preferences","source":"Settings","values":{}},{"key":"Toolbar.Mode.Vector","source":"Vector mode","values":{}},{"key":"Toolbar.Mode.Pixel","source":"Pixel mode","values":{}}]
diff --git a/rojo-test/build-test-snapshots/end_to_end__tests__build__csv_in_folder.snap b/rojo-test/build-test-snapshots/end_to_end__tests__build__csv_in_folder.snap
index 40bc7ce5..d60fca02 100644
--- a/rojo-test/build-test-snapshots/end_to_end__tests__build__csv_in_folder.snap
+++ b/rojo-test/build-test-snapshots/end_to_end__tests__build__csv_in_folder.snap
@@ -10,7 +10,7 @@ expression: contents
-
normal
- [{"example":"An exclamation of despair","key":"Ack","source":"Ack!","values":{"es":"¡Ay!"}}]
+ [{"key":"Ack","example":"An exclamation of despair","source":"Ack!","values":{"es":"¡Ay!"}}]
diff --git a/src/snapshot_middleware/csv.rs b/src/snapshot_middleware/csv.rs
index 8cea9b81..b4a86efc 100644
--- a/src/snapshot_middleware/csv.rs
+++ b/src/snapshot_middleware/csv.rs
@@ -162,11 +162,13 @@ pub fn syncback_csv_init<'sync>(
///
/// We manually deserialize into this table from CSV, but let serde_json handle
/// serialization.
-#[derive(Debug, Default, Deserialize)]
+#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct LocalizationEntry<'a> {
+ #[serde(skip_serializing_if = "Option::is_none")]
key: Option>,
+ #[serde(skip_serializing_if = "Option::is_none")]
context: Option>,
// Roblox writes `examples` for LocalizationTable's Content property, which
@@ -174,77 +176,16 @@ struct LocalizationEntry<'a> {
// This is reported here: https://devforum.roblox.com/t/2908720.
//
// To support their mistake, we support an alias named `examples`.
- #[serde(alias = "examples")]
+ #[serde(skip_serializing_if = "Option::is_none", alias = "examples")]
example: Option>,
+ #[serde(skip_serializing_if = "Option::is_none")]
source: Option>,
// We use a BTreeMap here to get deterministic output order.
values: BTreeMap, Cow<'a, str>>,
}
-// Guarantee a specific order of both fields and entries so that diff always match up
-impl<'a> Serialize for LocalizationEntry<'a> {
- fn serialize(&self, serializer: S) -> Result
- where
- S: serde::Serializer,
- {
- let mut btree = BTreeMap::new();
-
- if let Some(key) = &self.key {
- btree.insert(
- "key",
- serde_json::to_value(key).map_err(serde::ser::Error::custom)?,
- );
- }
-
- if let Some(context) = &self.context {
- btree.insert(
- "context",
- serde_json::to_value(context).map_err(serde::ser::Error::custom)?,
- );
- }
-
- if let Some(example) = &self.example {
- btree.insert(
- "example",
- serde_json::to_value(example).map_err(serde::ser::Error::custom)?,
- );
- }
-
- if let Some(source) = &self.source {
- btree.insert(
- "source",
- serde_json::to_value(source).map_err(serde::ser::Error::custom)?,
- );
- }
-
- btree.insert(
- "values",
- serde_json::to_value(&self.values).map_err(serde::ser::Error::custom)?,
- );
-
- btree.serialize(serializer)
- }
-}
-
-fn sort_localization_entries(mut entries: Vec) -> Vec {
- entries.sort_by(|a, b| {
- a.key
- .as_deref()
- .unwrap_or_default()
- .cmp(&b.key.as_deref().unwrap_or_default())
- .then_with(|| {
- a.source
- .as_deref()
- .unwrap_or_default()
- .cmp(&b.source.as_deref().unwrap_or_default())
- })
- });
-
- entries
-}
-
/// Normally, we'd be able to let the csv crate construct our struct for us.
///
/// However, because of a limitation with Serde's 'flatten' feature, it's not
@@ -295,8 +236,8 @@ fn convert_localization_csv(contents: &[u8]) -> anyhow::Result {
entries.push(entry);
}
- let encoded = serde_json::to_string(&sort_localization_entries(entries))
- .context("Could not encode JSON for localization table")?;
+ let encoded =
+ serde_json::to_string(&entries).context("Could not encode JSON for localization table")?;
Ok(encoded)
}
@@ -308,9 +249,11 @@ fn localization_to_csv(csv_contents: &str) -> anyhow::Result> {
let mut out = Vec::new();
let mut writer = csv::Writer::from_writer(&mut out);
- let csv: Vec = sort_localization_entries(
- serde_json::from_str(csv_contents).context("cannot decode JSON from localization table")?,
- );
+ let mut csv: Vec =
+ serde_json::from_str(csv_contents).context("cannot decode JSON from localization table")?;
+
+ // TODO sort this better
+ csv.sort_by(|a, b| a.source.partial_cmp(&b.source).unwrap());
let mut headers = vec!["Key", "Source", "Context", "Example"];
// We want both order and a lack of duplicates, so we use a BTreeSet.
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_from_vfs.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_from_vfs.snap
index aa4c18c8..6356256d 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_from_vfs.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_from_vfs.snap
@@ -20,5 +20,5 @@ name: foo
class_name: LocalizationTable
properties:
Contents:
- String: "[{\"example\":\"An exclamation of despair\",\"key\":\"Ack\",\"source\":\"Ack!\",\"values\":{\"es\":\"¡Ay!\"}}]"
+ String: "[{\"key\":\"Ack\",\"example\":\"An exclamation of despair\",\"source\":\"Ack!\",\"values\":{\"es\":\"¡Ay!\"}}]"
children: []
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init.snap
index 385666a8..813a2933 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init.snap
@@ -29,5 +29,5 @@ name: root
class_name: LocalizationTable
properties:
Contents:
- String: "[{\"example\":\"An exclamation of despair\",\"key\":\"Ack\",\"source\":\"Ack!\",\"values\":{\"es\":\"¡Ay!\"}}]"
+ String: "[{\"key\":\"Ack\",\"example\":\"An exclamation of despair\",\"source\":\"Ack!\",\"values\":{\"es\":\"¡Ay!\"}}]"
children: []
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init_with_meta.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init_with_meta.snap
index 81fe3351..01ce0a0f 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init_with_meta.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_init_with_meta.snap
@@ -29,5 +29,5 @@ name: root
class_name: LocalizationTable
properties:
Contents:
- String: "[{\"example\":\"An exclamation of despair\",\"key\":\"Ack\",\"source\":\"Ack!\",\"values\":{\"es\":\"¡Ay!\"}}]"
+ String: "[{\"key\":\"Ack\",\"example\":\"An exclamation of despair\",\"source\":\"Ack!\",\"values\":{\"es\":\"¡Ay!\"}}]"
children: []
diff --git a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_with_meta.snap b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_with_meta.snap
index b02dee1c..f90eccc8 100644
--- a/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_with_meta.snap
+++ b/src/snapshot_middleware/snapshots/librojo__snapshot_middleware__csv__test__csv_with_meta.snap
@@ -20,5 +20,5 @@ name: foo
class_name: LocalizationTable
properties:
Contents:
- String: "[{\"example\":\"An exclamation of despair\",\"key\":\"Ack\",\"source\":\"Ack!\",\"values\":{\"es\":\"¡Ay!\"}}]"
+ String: "[{\"key\":\"Ack\",\"example\":\"An exclamation of despair\",\"source\":\"Ack!\",\"values\":{\"es\":\"¡Ay!\"}}]"
children: []