"
data-action="input->cost-basis-form#updateTotal"
data-cost-basis-form-target="perShare">
diff --git a/app/views/holdings/show.html.erb b/app/views/holdings/show.html.erb
index c94582471..38ff086c0 100644
--- a/app/views/holdings/show.html.erb
+++ b/app/views/holdings/show.html.erb
@@ -133,12 +133,12 @@
<%= currency.symbol %>
-
"
data-action="input->drawer-cost-basis#updatePerShare"
data-drawer-cost-basis-target="total">
<%= currency.iso_code %>
@@ -153,11 +153,11 @@
<%= t("holdings.cost_basis_cell.or_per_share_label") %>
<%= currency.symbol %>
- "
data-action="input->drawer-cost-basis#updateTotal"
data-drawer-cost-basis-target="perShare">
<%= currency.iso_code %>
diff --git a/app/views/trades/_header.html.erb b/app/views/trades/_header.html.erb
index 3cd66872e..a9f1e12bb 100644
--- a/app/views/trades/_header.html.erb
+++ b/app/views/trades/_header.html.erb
@@ -18,7 +18,7 @@
<% if entry.linked? %>
-
+ ">
<%= icon("refresh-ccw", size: "sm") %>
<% end %>
diff --git a/app/views/transactions/_header.html.erb b/app/views/transactions/_header.html.erb
index f91f5d0ee..d80c13bed 100644
--- a/app/views/transactions/_header.html.erb
+++ b/app/views/transactions/_header.html.erb
@@ -12,7 +12,7 @@
<%= icon "arrow-left-right", size: "sm", class: "text-secondary" %>
<% end %>
<% if entry.linked? %>
- " class="text-secondary">
+ " class="text-secondary">
<%= icon("refresh-ccw", size: "sm") %>
<% end %>
diff --git a/config/locales/views/transactions/ca.yml b/config/locales/views/transactions/ca.yml
index 7a1a2ad04..dc3b94981 100644
--- a/config/locales/views/transactions/ca.yml
+++ b/config/locales/views/transactions/ca.yml
@@ -122,6 +122,7 @@ ca:
transaction:
pending: Pendent
pending_tooltip: Transacció pendent — pot canviar quan es publiqui
+ linked_with_provider: Vinculat amb %{provider}
possible_duplicate: Duplicat?
potential_duplicate_tooltip: Això pot ser un duplicat d'una altra transacció
review_recommended: Revisa
diff --git a/config/locales/views/transactions/de.yml b/config/locales/views/transactions/de.yml
index a8e280497..2b19d6942 100644
--- a/config/locales/views/transactions/de.yml
+++ b/config/locales/views/transactions/de.yml
@@ -75,7 +75,7 @@ de:
transaction:
pending: Ausstehend
pending_tooltip: Ausstehende Transaktion — kann sich bei Buchung ändern
- linked_with_plaid: Mit Plaid verknüpft
+ linked_with_provider: Mit %{provider} verknüpft
activity_type_tooltip: Art der Anlageaktivität
possible_duplicate: Duplikat?
potential_duplicate_tooltip: Dies könnte ein Duplikat einer anderen Transaktion sein
diff --git a/config/locales/views/transactions/en.yml b/config/locales/views/transactions/en.yml
index 836aabff3..0c4b5b990 100644
--- a/config/locales/views/transactions/en.yml
+++ b/config/locales/views/transactions/en.yml
@@ -86,7 +86,7 @@ en:
transaction:
pending: Pending
pending_tooltip: Pending transaction — may change when posted
- linked_with_plaid: Linked with Plaid
+ linked_with_provider: Linked with %{provider}
activity_type_tooltip: Investment activity type
possible_duplicate: Duplicate?
potential_duplicate_tooltip: This may be a duplicate of another transaction
diff --git a/config/locales/views/transactions/es.yml b/config/locales/views/transactions/es.yml
index 7e0ea10dc..b5d294422 100644
--- a/config/locales/views/transactions/es.yml
+++ b/config/locales/views/transactions/es.yml
@@ -76,7 +76,7 @@ es:
transaction:
pending: Pendiente
pending_tooltip: Transacción pendiente — puede cambiar al confirmarse
- linked_with_plaid: Vinculado con Plaid
+ linked_with_provider: Vinculado con %{provider}
activity_type_tooltip: Tipo de actividad de inversión
possible_duplicate: ¿Duplicada?
potential_duplicate_tooltip: Esto puede ser un duplicado de otra transacción
diff --git a/config/locales/views/transactions/fr.yml b/config/locales/views/transactions/fr.yml
index e4f6960e9..d6aeb52a3 100644
--- a/config/locales/views/transactions/fr.yml
+++ b/config/locales/views/transactions/fr.yml
@@ -45,6 +45,8 @@ fr:
edit_merchants: Modifier les marchands
edit_tags: Modifier les étiquettes
import: Importer
+ transaction:
+ linked_with_provider: Lié avec %{provider}
index:
transaction: transaction
transactions: transactions
diff --git a/config/locales/views/transactions/nb.yml b/config/locales/views/transactions/nb.yml
index 5661ad062..479659c30 100644
--- a/config/locales/views/transactions/nb.yml
+++ b/config/locales/views/transactions/nb.yml
@@ -46,6 +46,8 @@ nb:
edit_merchants: Rediger selgere
edit_tags: Rediger tagger
import: Importer
+ transaction:
+ linked_with_provider: Koblet med %{provider}
index:
transaction: transaksjon
transactions: transaksjoner
diff --git a/config/locales/views/transactions/nl.yml b/config/locales/views/transactions/nl.yml
index 4e3bb1dfc..25415e4fc 100644
--- a/config/locales/views/transactions/nl.yml
+++ b/config/locales/views/transactions/nl.yml
@@ -74,6 +74,7 @@ nl:
transaction:
pending: Wachtend
pending_tooltip: Wachtende transactie — kan wijzigen bij posting
+ linked_with_provider: Gekoppeld met %{provider}
activity_type_tooltip: Beleggingsactiviteitstype
possible_duplicate: Duplicaat?
potential_duplicate_tooltip: Dit kan een duplicaat zijn van een andere transactie
diff --git a/config/locales/views/transactions/pt-BR.yml b/config/locales/views/transactions/pt-BR.yml
index 15476c952..5d898a27a 100644
--- a/config/locales/views/transactions/pt-BR.yml
+++ b/config/locales/views/transactions/pt-BR.yml
@@ -49,6 +49,8 @@ pt-BR:
edit_merchants: Editar comerciantes
edit_tags: Editar tags
import: Importar
+ transaction:
+ linked_with_provider: Vinculado com %{provider}
index:
transaction: transação
transactions: transações
diff --git a/config/locales/views/transactions/ro.yml b/config/locales/views/transactions/ro.yml
index 8312d3406..dd0d71087 100644
--- a/config/locales/views/transactions/ro.yml
+++ b/config/locales/views/transactions/ro.yml
@@ -45,6 +45,8 @@ ro:
edit_merchants: Editează comercianți
edit_tags: Editează etichete
import: Importă
+ transaction:
+ linked_with_provider: Conectat cu %{provider}
index:
transaction: tranzacție
transactions: tranzacții
diff --git a/config/locales/views/transactions/tr.yml b/config/locales/views/transactions/tr.yml
index b7f545a8e..bb27ce92c 100644
--- a/config/locales/views/transactions/tr.yml
+++ b/config/locales/views/transactions/tr.yml
@@ -45,6 +45,8 @@ tr:
edit_merchants: Satıcıları düzenle
edit_tags: Etiketleri düzenle
import: İçe aktar
+ transaction:
+ linked_with_provider: "%{provider} ile bağlantılı"
index:
transaction: işlem
transactions: işlemler
diff --git a/config/locales/views/transactions/zh-CN.yml b/config/locales/views/transactions/zh-CN.yml
index b8533bba2..11fa2e3e5 100644
--- a/config/locales/views/transactions/zh-CN.yml
+++ b/config/locales/views/transactions/zh-CN.yml
@@ -25,6 +25,8 @@ zh-CN:
edit_merchants: 编辑商户
edit_tags: 编辑标签
import: 导入
+ transaction:
+ linked_with_provider: 已与 %{provider} 关联
index:
import: 导入
transaction: 交易
diff --git a/config/locales/views/transactions/zh-TW.yml b/config/locales/views/transactions/zh-TW.yml
index d5bb0fb46..53c080a8e 100644
--- a/config/locales/views/transactions/zh-TW.yml
+++ b/config/locales/views/transactions/zh-TW.yml
@@ -48,6 +48,8 @@ zh-TW:
edit_merchants: 編輯商家
edit_tags: 編輯標籤
import: 匯入
+ transaction:
+ linked_with_provider: 已與 %{provider} 連結
index:
transaction: 筆交易
transactions: 筆交易
diff --git a/test/javascript/parse_locale_float_test.mjs b/test/javascript/parse_locale_float_test.mjs
index cc233e25e..5d88d9e55 100644
--- a/test/javascript/parse_locale_float_test.mjs
+++ b/test/javascript/parse_locale_float_test.mjs
@@ -1,11 +1,20 @@
import { describe, it } from "node:test"
import assert from "node:assert/strict"
-// Inline the function to avoid needing a bundler for ESM imports
-function parseLocaleFloat(value) {
+// Inline the function to avoid needing a bundler for ESM imports.
+// Must be kept in sync with app/javascript/utils/parse_locale_float.js
+function parseLocaleFloat(value, { separator } = {}) {
if (typeof value !== "string") return Number.parseFloat(value) || 0
const cleaned = value.replace(/\s/g, "")
+
+ if (separator === ",") {
+ return Number.parseFloat(cleaned.replace(/\./g, "").replace(",", ".")) || 0
+ }
+ if (separator === ".") {
+ return Number.parseFloat(cleaned.replace(/,/g, "")) || 0
+ }
+
const lastComma = cleaned.lastIndexOf(",")
const lastDot = cleaned.lastIndexOf(".")
@@ -74,6 +83,10 @@ describe("parseLocaleFloat", () => {
it("treats 1,000 as one thousand", () => {
assert.equal(parseLocaleFloat("1,000"), 1000)
})
+
+ it("treats 1,000,000 as one million", () => {
+ assert.equal(parseLocaleFloat("1,000,000"), 1000000)
+ })
})
describe("integers", () => {
@@ -96,6 +109,79 @@ describe("parseLocaleFloat", () => {
})
})
+ describe("negative numbers", () => {
+ it("parses negative dot-decimal", () => {
+ assert.equal(parseLocaleFloat("-1,234.56"), -1234.56)
+ })
+
+ it("parses negative comma-decimal", () => {
+ assert.equal(parseLocaleFloat("-1.234,56"), -1234.56)
+ })
+
+ it("parses simple negative", () => {
+ assert.equal(parseLocaleFloat("-256.54"), -256.54)
+ })
+
+ it("parses negative European simple", () => {
+ assert.equal(parseLocaleFloat("-256,54"), -256.54)
+ })
+ })
+
+ describe("with separator hint", () => {
+ describe("comma separator (European currencies like EUR)", () => {
+ const opts = { separator: "," }
+
+ it("disambiguates 1,234 as 1.234 (European decimal)", () => {
+ assert.equal(parseLocaleFloat("1,234", opts), 1.234)
+ })
+
+ it("parses 1.234,56 correctly", () => {
+ assert.equal(parseLocaleFloat("1.234,56", opts), 1234.56)
+ })
+
+ it("parses simple comma decimal", () => {
+ assert.equal(parseLocaleFloat("256,54", opts), 256.54)
+ })
+
+ it("parses integer without separators", () => {
+ assert.equal(parseLocaleFloat("1234", opts), 1234)
+ })
+
+ it("parses negative value", () => {
+ assert.equal(parseLocaleFloat("-1.234,56", opts), -1234.56)
+ })
+ })
+
+ describe("dot separator (English currencies like USD)", () => {
+ const opts = { separator: "." }
+
+ it("disambiguates 1,234 as 1234 (English thousands)", () => {
+ assert.equal(parseLocaleFloat("1,234", opts), 1234)
+ })
+
+ it("parses 1,234.56 correctly", () => {
+ assert.equal(parseLocaleFloat("1,234.56", opts), 1234.56)
+ })
+
+ it("parses simple dot decimal", () => {
+ assert.equal(parseLocaleFloat("256.54", opts), 256.54)
+ })
+
+ it("parses integer without separators", () => {
+ assert.equal(parseLocaleFloat("1234", opts), 1234)
+ })
+
+ it("parses negative value", () => {
+ assert.equal(parseLocaleFloat("-1,234.56", opts), -1234.56)
+ })
+ })
+
+ it("falls back to heuristic when no hint given", () => {
+ assert.equal(parseLocaleFloat("1,234"), 1234)
+ assert.equal(parseLocaleFloat("256,54"), 256.54)
+ })
+ })
+
describe("edge cases", () => {
it("returns 0 for empty string", () => {
assert.equal(parseLocaleFloat(""), 0)