<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged localization</title>
<link>https://coderlegion.com/tag/localization</link>
<description>Powered by Question2Answer</description>
<item>
<title>Convert-As-You-Type Currency Conversion in Swift: Decimal Maths, Cached Rates and Formatting</title>
<link>https://coderlegion.com/32387/convert-as-you-type-currency-conversion-swift-decimal-maths-cached-rates-and-formatting</link>
<description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://bestlatestapps.com/blog/convert-as-you-type-currency-conversion-in-swift/&quot;&gt;https://bestlatestapps.com/blog/convert-as-you-type-currency-conversion-in-swift/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Why Double is the wrong type for money, how to derive any cross rate from one base, keeping rates fresh without a refresh button, and formatting 150+ currencies correctly&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bestlatestapps.com/assets/blog/convert-as-you-type-currency-conversion-in-swift/currency-converter-icon-512.jpg&quot; alt=&quot;Currency Converter : Fast app icon&quot;&gt;&lt;/p&gt;
&lt;p&gt;A currency converter is used at the worst possible moments: at a market stall abroad, at a checkout page in another currency, in a taxi with a meter running. Nobody wants to tap &quot;Convert&quot;. Nobody wants a spinner. And nobody wants to see &lt;code&gt;0.30000000000000004&lt;/code&gt; on the screen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Currency Converter : Fast&lt;/strong&gt; is one of my first apps, published in April 2020. Its listing describes the whole philosophy in a few lines: 150+ currencies, &quot;converts as you type (no need for a 'Convert' button!)&quot;, exchange rates &quot;updated automatically — you don't have to do anything!&quot;, and 3 decimal places &quot;for greater accuracy&quot;.&lt;/p&gt;
&lt;p&gt;Each of those promises maps onto a specific technical decision: the number type, the rate model, caching and refresh, and formatting. This article goes through how I'd build each one in Swift. API names and availability are checked against Apple's documentation. I describe the approach I'd use rather than the app's internals, and I deliberately don't name a rate provider: the listing doesn't, and the design is the same whichever one you use.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-money-is-not-a-double&quot;&gt;1. Money is not a Double&lt;/h3&gt;
&lt;p&gt;Binary floating point can't represent most decimal fractions exactly. &lt;code&gt;0.1 + 0.2&lt;/code&gt; isn't &lt;code&gt;0.3&lt;/code&gt;, and after a few multiplications by exchange rates, the error shows up in exactly the decimal places you're displaying.&lt;/p&gt;
&lt;p&gt;Foundation's &lt;code&gt;Decimal&lt;/code&gt; is a base-10 number type, so &lt;code&gt;0.1&lt;/code&gt; really is 0.1:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Foundation

let a: Double = 0.1 + 0.2
print(a)                                  // 0.30000000000000004

let b = Decimal(string: &quot;0.1&quot;)! + Decimal(string: &quot;0.2&quot;)!
print(b)                                  // 0.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rule I follow: &lt;strong&gt;amounts and rates are &lt;code&gt;Decimal&lt;/code&gt; from the moment they enter the app&lt;/strong&gt;, whether that's the user's typing or the JSON from the rates API, until the moment they're formatted for display. Two gotchas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Decimal(0.1)&lt;/code&gt; converts &lt;em&gt;from a Double&lt;/em&gt;, so the binary error comes with it. Initialise from strings: &lt;code&gt;Decimal(string:locale:)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JSONDecoder&lt;/code&gt; decodes &lt;code&gt;Decimal&lt;/code&gt; fields via &lt;code&gt;Double&lt;/code&gt; internally on some systems, which can reintroduce error in the last digits. If exactness matters, decode rates as &lt;code&gt;String&lt;/code&gt;s and convert them yourself, as below.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-one-base-every-cross-rate&quot;&gt;2. One base, every cross rate&lt;/h3&gt;
&lt;p&gt;You don't need a 150 × 150 table. Fetch every currency's rate against &lt;strong&gt;one base&lt;/strong&gt; (say USD), and derive any pair:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;amount_in_B = amount_in_A × (rate_B / rate_A)       where rate_X = units of X per 1 base
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Foundation

struct RateTable: Codable {
    let base: String                     // &quot;USD&quot;
    let rates: [String: Decimal]         // &quot;EUR&quot;: 0.92 means 1 USD = 0.92 EUR
    let fetchedAt: Date

    func rate(from: String, to: String) -&amp;gt; Decimal? {
        guard let a = from == base ? 1 : rates[from],
              let b = to == base ? 1 : rates[to],
              a != 0 else { return nil }
        return b / a
    }

    func convert(_ amount: Decimal, from: String, to: String) -&amp;gt; Decimal? {
        rate(from: from, to: to).map { amount * $0 }
    }
}

// Decode rates exactly: the API sends numbers, but we read them as strings first.
struct RatesResponse: Decodable {
    let base: String
    let rates: [String: Decimal]

    private enum CodingKeys: String, CodingKey { case base, rates }

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        base = try c.decode(String.self, forKey: .base)
        // Assumes the provider sends rates as JSON strings; if it sends numbers,
        // decode [String: Double] and convert via &quot;\(value)&quot; to limit binary error.
        let raw = try c.decode([String: String].self, forKey: .rates)
        rates = raw.compactMapValues { Decimal(string: $0, locale: Locale(identifier: &quot;en_US_POSIX&quot;)) }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;RateTable.rate(from:to:)&lt;/code&gt; is also how you show &quot;the current exchange rate of what you're converting&quot;, one of the listing's features: &lt;code&gt;1 EUR = 1.087 USD&lt;/code&gt;, computed from the same table.&lt;/p&gt;
&lt;p&gt;Note the &lt;code&gt;en_US_POSIX&lt;/code&gt; locale when parsing API data. API numbers always use &lt;code&gt;.&lt;/code&gt; as the decimal separator, whatever the user's device language is.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-convert-as-you-type&quot;&gt;3. Convert as you type&lt;/h3&gt;
&lt;p&gt;With &lt;code&gt;Decimal&lt;/code&gt; maths and a local rate table, conversion is instant and synchronous. There's no reason for a button. In SwiftUI:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import SwiftUI

@MainActor
final class ConverterModel: ObservableObject {
    @Published var input = &quot;&quot;
    @Published var from = &quot;USD&quot;
    @Published var to = &quot;EUR&quot;
    @Published private(set) var table: RateTable?

    private let parser: NumberFormatter = {
        let f = NumberFormatter()
        f.numberStyle = .decimal
        f.generatesDecimalNumbers = true   // parse straight to NSDecimalNumber, never via Double
        f.isLenient = true
        return f
    }()

    var amount: Decimal? {
        (parser.number(from: input) as? NSDecimalNumber)?.decimalValue
    }

    var result: Decimal? {
        guard let amount, let table else { return nil }
        return table.convert(amount, from: from, to: to)
    }

    func swap() { (from, to) = (to, from) }
}

struct ConverterView: View {
    @StateObject private var model = ConverterModel()

    var body: some View {
        Form {
            Section {
                TextField(&quot;Amount&quot;, text: $model.input)
                    .keyboardType(.decimalPad)
                    .font(.largeTitle.monospacedDigit())
                CurrencyPicker(title: &quot;From&quot;, code: $model.from)
            }
            Section {
                Text(model.result.map { CurrencyText.format($0, code: model.to) } ?? &quot;—&quot;)
                    .font(.largeTitle.monospacedDigit())
                    .contentTransition(.numericText())          // iOS 17: digits roll smoothly
                    .animation(.snappy, value: model.result)
                CurrencyPicker(title: &quot;To&quot;, code: $model.to)
            }
            Button(&quot;Swap&quot;, systemImage: &quot;arrow.up.arrow.down&quot;, action: model.swap)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because &lt;code&gt;result&lt;/code&gt; is a computed property, every keystroke recomputes it: there's no debounce and no task. &lt;code&gt;CurrencyPicker&lt;/code&gt; is a searchable list of codes (section 5).&lt;/p&gt;
&lt;p&gt;The decimal-pad keyboard shows the user's locale separator, and the &lt;code&gt;NumberFormatter&lt;/code&gt; uses &lt;code&gt;Locale.current&lt;/code&gt; by default, so &quot;12,50&quot; in Germany and &quot;12.50&quot; in the US both parse.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-rates-that-update-themselves-and-work-offline&quot;&gt;4. Rates that update themselves, and work offline&lt;/h3&gt;
&lt;p&gt;&quot;Exchange rates are updated automatically&quot; has three parts: a cache that's always there, a refresh when the app comes to the foreground, and an occasional refresh in the background.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Foundation

actor RateStore {
    static let shared = RateStore()
    private let fileURL: URL = FileManager.default
        .urls(for: .cachesDirectory, in: .userDomainMask)[0]
        .appendingPathComponent(&quot;rates.json&quot;)
    private let maxAge: TimeInterval = 60 * 60          // refresh if older than an hour

    /// Always returns something if we've ever succeeded, even offline.
    func cached() -&amp;gt; RateTable? {
        guard let data = try? Data(contentsOf: fileURL) else { return nil }
        return try? JSONDecoder().decode(RateTable.self, from: data)
    }

    func refreshIfStale(force: Bool = false) async -&amp;gt; RateTable? {
        if !force, let c = cached(), Date().timeIntervalSince(c.fetchedAt) &amp;lt; maxAge { return c }
        do {
            var request = URLRequest(url: URL(string: &quot;https://rates.example.com/latest?base=USD&quot;)!)
            request.timeoutInterval = 10
            let (data, response) = try await URLSession.shared.data(for: request)
            guard (response as? HTTPURLResponse)?.statusCode == 200 else { return cached() }
            let decoded = try JSONDecoder().decode(RatesResponse.self, from: data)
            let table = RateTable(base: decoded.base, rates: decoded.rates, fetchedAt: Date())
            try JSONEncoder().encode(table).write(to: fileURL, options: .atomic)
            return table
        } catch {
            return cached()                                   // offline or failed: keep the last good table
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Key behaviours:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Show the cache immediately&lt;/strong&gt;, then refresh. The UI never waits on the network.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;On failure, keep the last good table.&lt;/strong&gt; A slightly old rate is far more useful than an error screen at a market stall.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Show the age.&lt;/strong&gt; &quot;Rates updated 3 hours ago&quot; in small text is honest, and it's the one piece of UI that explains a surprising number.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bundle a seed table&lt;/strong&gt; in the app, so a first launch in Airplane Mode still converts. Label it with its date.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For background refresh, SwiftUI's &lt;code&gt;backgroundTask(_:action:)&lt;/code&gt; scene modifier (iOS 16+) with &lt;code&gt;.appRefresh&lt;/code&gt; pairs with &lt;code&gt;BGAppRefreshTaskRequest&lt;/code&gt; (iOS 13+):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import SwiftUI
import BackgroundTasks

@main
struct CurrencyApp: App {
    @Environment(\.scenePhase) private var phase

    var body: some Scene {
        WindowGroup { ConverterView() }
            .onChange(of: phase) { _, newPhase in
                if newPhase == .active { Task { _ = await RateStore.shared.refreshIfStale() } }
                if newPhase == .background { scheduleRefresh() }
            }
            .backgroundTask(.appRefresh(&quot;rates.refresh&quot;)) {
                _ = await RateStore.shared.refreshIfStale(force: true)
                scheduleRefresh()
            }
    }
}

func scheduleRefresh() {
    let request = BGAppRefreshTaskRequest(identifier: &quot;rates.refresh&quot;)
    request.earliestBeginDate = Date(timeIntervalSinceNow: 4 * 60 * 60)
    try? BGTaskScheduler.shared.submit(request)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add &lt;code&gt;rates.refresh&lt;/code&gt; to &lt;code&gt;BGTaskSchedulerPermittedIdentifiers&lt;/code&gt; in Info.plist and enable Background Modes → Background fetch. Remember that &lt;code&gt;earliestBeginDate&lt;/code&gt; is a floor, not a schedule: the system decides when, based on how the user actually uses the app. The foreground refresh is what really keeps rates fresh.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-150-currencies-names-symbols-and-decimals&quot;&gt;5. 150+ currencies: names, symbols and decimals&lt;/h3&gt;
&lt;p&gt;Foundation knows a great deal about currencies, so you don't have to maintain that list by hand:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Foundation

struct CurrencyInfo: Identifiable, Hashable {
    let code: String
    var id: String { code }
    var name: String { Locale.current.localizedString(forCurrencyCode: code) ?? code }
}

enum CurrencyCatalog {
    /// Intersect what the rates API supports with what Foundation can name.
    static func available(in table: RateTable) -&amp;gt; [CurrencyInfo] {
        let common = Set(Locale.commonISOCurrencyCodes)          // iOS 16+
        return (Array(table.rates.keys) + [table.base])
            .filter(common.contains)
            .map(CurrencyInfo.init)
            .sorted { $0.name.localizedCompare($1.name) == .orderedAscending }
    }
}

enum CurrencyText {
    /// Three decimals, as the listing promises, with the locale's grouping and symbol.
    static func format(_ value: Decimal, code: String) -&amp;gt; String {
        value.formatted(.currency(code: code).precision(.fractionLength(3)))
    }
}

// CurrencyText.format(1234.5678, code: &quot;EUR&quot;) → &quot;€1,234.568&quot; in en_US, &quot;1.234,568 €&quot; in de_DE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;localizedString(forCurrencyCode:)&lt;/code&gt; gives &quot;Colombian Peso&quot; in English and &quot;Peso colombiano&quot; in Spanish for free. &lt;code&gt;Decimal.FormatStyle.Currency&lt;/code&gt; (iOS 15+) handles the symbol, its position, grouping and the decimal separator for the user's locale.&lt;/p&gt;
&lt;p&gt;About &lt;strong&gt;three decimal places&lt;/strong&gt;: most currencies have two minor units, some have zero (JPY, KRW) and a few have three (KWD, BHD). Showing three everywhere is a deliberate product choice. It gives more precision on small amounts and on the exchange rate itself. If you'd rather respect each currency's standard, use &lt;code&gt;.precision(.fractionLength(0...3))&lt;/code&gt; with rounding, or read the currency's default fraction digits from a &lt;code&gt;NumberFormatter&lt;/code&gt; set to &lt;code&gt;.currency&lt;/code&gt; style.&lt;/p&gt;
&lt;p&gt;Make the picker &lt;strong&gt;searchable by code and by name&lt;/strong&gt; (&lt;code&gt;.searchable(text:)&lt;/code&gt;), and put recently used currencies at the top. Most people convert between the same two or three currencies over and over.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-real-world-example-currency-converter-fast&quot;&gt;6. Real-world example: Currency Converter : Fast&lt;/h3&gt;
&lt;p&gt;This is the approach I'd use, and recommend, for &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://apps.apple.com/us/app/currency-converter-fast/id1509291290&quot;&gt;Currency Converter : Fast&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bestlatestapps.com/assets/blog/convert-as-you-type-currency-conversion-in-swift/currency-converter-screenshot-1.jpg&quot; alt=&quot;Currency Converter App Store screenshot 1&quot;&gt;&lt;/p&gt;
&lt;p&gt;At the time of writing, its App Store listing shows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Free&lt;/strong&gt;, in &lt;strong&gt;Finance&lt;/strong&gt; and &lt;strong&gt;Productivity&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;First released in &lt;strong&gt;April 2020&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;7 ratings&lt;/strong&gt; in the US store, averaging about &lt;strong&gt;4.7 stars&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Supports &lt;strong&gt;iOS 17.6 and later&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How the listing's features map to this article:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;What the listing says &lt;/th&gt;&lt;th&gt;Where it's covered&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&quot;Over 150 currencies supported&quot; / &quot;from the US Dollar to the Colombian Peso&quot; &lt;/td&gt;&lt;td&gt;One-base rate table plus Foundation's currency names (sections 2, 5)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Converts as you type (no need for a 'Convert' button!)&quot; &lt;/td&gt;&lt;td&gt;Synchronous &lt;code&gt;Decimal&lt;/code&gt; conversion as a computed property (section 3)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Exchange rates are updated automatically – you don't have to do anything!&quot; &lt;/td&gt;&lt;td&gt;Cache-first store, foreground refresh, background app refresh (section 4)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Uses 3 decimal places for greater accuracy!&quot; &lt;/td&gt;&lt;td&gt;&lt;code&gt;Decimal&lt;/code&gt; maths and &lt;code&gt;.precision(.fractionLength(3))&lt;/code&gt; (sections 1, 5)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&quot;Find the current exchange rate of what you're converting&quot; &lt;/td&gt;&lt;td&gt;&lt;code&gt;RateTable.rate(from:to:)&lt;/code&gt; (section 2)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;https://bestlatestapps.com/assets/blog/convert-as-you-type-currency-conversion-in-swift/currency-converter-screenshot-2.jpg&quot; alt=&quot;Currency Converter App Store screenshot 2&quot;&gt;&lt;/p&gt;
&lt;p&gt;Because the app targets iOS 17.6 and later, everything above, including &lt;code&gt;contentTransition(.numericText())&lt;/code&gt;, the two-parameter &lt;code&gt;onChange&lt;/code&gt; and the SwiftUI background task, is available without availability checks.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;7-before-you-ship-a-currency-converter-a-checklist&quot;&gt;7. Before you ship a currency converter: a checklist&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Decimal&lt;/code&gt; end to end.&lt;/strong&gt; No &lt;code&gt;Double&lt;/code&gt; between parsing and formatting, and no &lt;code&gt;Decimal(someDouble)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API numbers are parsed with &lt;code&gt;en_US_POSIX&lt;/code&gt;&lt;/strong&gt;, and user input with the user's locale.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross rates come from one base.&lt;/strong&gt; Test that A → B → A returns the original amount, within rounding.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The cache is shown first.&lt;/strong&gt; Launch in Airplane Mode: you see the last rates and their age, not an error.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A seed table ships in the bundle&lt;/strong&gt; for first-launch offline use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Background refresh is configured&lt;/strong&gt; (permitted identifiers, background mode), and foreground refresh does the real work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Formatting is tested in several locales&lt;/strong&gt;: en_US, de_DE, fr_FR, ja_JP and ar, at least.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero-decimal and three-decimal currencies look right&lt;/strong&gt;, according to your product decision.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rates are labelled honestly.&lt;/strong&gt; These are indicative mid-market-style rates, not what a bank or card will charge.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h3&gt;
&lt;p&gt;The &quot;fast&quot; in a fast currency converter isn't about network speed. It's about never needing the network at the moment of use. Keep money in &lt;code&gt;Decimal&lt;/code&gt;, derive every pair from one cached base table, refresh quietly in the foreground and background, and let Foundation handle names, symbols and locale formatting. The result converts as you type, anywhere, even in Airplane Mode.&lt;/p&gt;
&lt;p&gt;Try it, or explore my other apps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Currency Converter : Fast (iOS):&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://apps.apple.com/us/app/currency-converter-fast/id1509291290&quot;&gt;https://apps.apple.com/us/app/currency-converter-fast/id1509291290&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;All of my free iOS apps:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://apps.apple.com/us/developer/alok-singh/id1508795536&quot;&gt;https://apps.apple.com/us/developer/alok-singh/id1508795536&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;My portfolio:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://bestlatestapps.com/alok/&quot;&gt;https://bestlatestapps.com/alok/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How do you handle money in Swift: &lt;code&gt;Decimal&lt;/code&gt;, integer minor units, or a library? I'd love to hear the trade-offs you've found in the comments.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;— Alok Kumar Singh&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://bestlatestapps.com/blog/convert-as-you-type-currency-conversion-in-swift/&quot;&gt;https://bestlatestapps.com/blog/convert-as-you-type-currency-conversion-in-swift/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32387/convert-as-you-type-currency-conversion-swift-decimal-maths-cached-rates-and-formatting</guid>
<pubDate>Sun, 25 Oct 2026 12:40:42 +0000</pubDate>
</item>
<item>
<title>Translation Memory untuk Tim Penerjemahan: Desain Data, Versioning dengan Git, dan QA Otomatis</title>
<link>https://coderlegion.com/12156/translation-memory-untuk-tim-penerjemahan-desain-data-versioning-dengan-git-dan-otomatis</link>
<description>&lt;p&gt;Kalau kamu pernah mengelola dokumentasi bilingual (mis. Jepang–Indonesia) di repo yang terus berubah, kamu tahu rasanya: istilah teknis gonta-ganti, kalimat “sudah benar minggu lalu” tiba-tiba jadi salah, dan revisi kecil bisa memicu efek domino. Menariknya, tim seperti GitLab juga mengangkat pola otomasi dokumentasi dan workflow lokalisasi yang makin &lt;em&gt;developer-friendly&lt;/em&gt;—misalnya bagaimana proses docs dan bahasa diintegrasikan dengan pipeline modern (lihat cerita mereka di &lt;a rel=&quot;nofollow&quot; href=&quot;https://about.gitlab.com/blog/how-we-built-and-automated-our-new-japanese-gitlab-docs-site/&quot;&gt;otomasi dokumentasi GitLab berbahasa Jepang&lt;/a&gt;). Di titik ini, yang kamu butuhkan bukan sekadar “penerjemah yang cepat”, tapi sistem yang bisa diaudit, diuji, dan di-&lt;em&gt;rollback&lt;/em&gt;—alias &lt;strong&gt;translation memory qa git&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Secara ilmiah, praktik mengelola &lt;em&gt;translation memory&lt;/em&gt; (TM), kualitas terjemahan, dan konsistensi terminologi sudah lama diteliti di ranah NLP/MT dan tooling penerjemahan—termasuk studi yang membahas pendekatan terkait &lt;em&gt;translation memory&lt;/em&gt; dalam ekosistem &lt;em&gt;language technology&lt;/em&gt; (rujukan: &lt;a rel=&quot;nofollow&quot; href=&quot;https://aclanthology.org/2022.eurali-1.6.pdf&quot;&gt;paper di ACL Anthology ini&lt;/a&gt;). Kenapa tema ini layak diangkat untuk pembaca Coderlegion? Karena semakin banyak tim dev yang “dipaksa” menjadi tim lokalisasi dadakan: produk &lt;em&gt;ship&lt;/em&gt; cepat, UI copy berubah setiap sprint, dan QA bahasa harus mengikuti ritme CI/CD.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“Kalau terjemahanmu tidak bisa diuji seperti kode, kamu sedang menyimpan bug—hanya saja bentuknya kalimat.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;1-masalah-nyata-terjemahan-itu-bukan-file-statis&quot;&gt;1. Masalah Nyata: Terjemahan Itu Bukan File Statis&lt;/h3&gt;
&lt;p&gt;Terjemahan di tim modern bergerak seperti software: ada &lt;em&gt;release&lt;/em&gt;, ada regresi, ada dependensi. Bedanya, bug bahasa sering lolos karena “tidak meledak” di runtime—tapi meledak di user trust.&lt;/p&gt;
&lt;h4 id=&quot;tantangan-yang-sering-muncul&quot;&gt;Tantangan yang sering muncul&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Istilah tidak konsisten&lt;/strong&gt; (contoh: “pengguna”, “user”, “pemakai”).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Perubahan kecil = biaya besar&lt;/strong&gt; (satu istilah berubah, 120 string ikut terdampak).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review tidak terstruktur&lt;/strong&gt; (komentar menyebar di chat, tidak ada jejak keputusan).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tidak ada definisi kualitas&lt;/strong&gt;: “bagus” menurut siapa?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;kenapa-dev-perlu-peduli-&quot;&gt;Kenapa dev perlu peduli?&lt;/h4&gt;
&lt;p&gt;Karena pada akhirnya lokalisasi adalah bagian dari &lt;em&gt;product quality&lt;/em&gt;. Kalau kamu sudah punya standard untuk code style, linter, unit test—kenapa bahasa dibiarkan “feel-based”? Inilah momen tepat untuk membawa &lt;em&gt;engineering discipline&lt;/em&gt; ke workflow bahasa—mulai dari TM sampai QA.&lt;/p&gt;
&lt;h3 id=&quot;2-translation-memory-sebagai-database-perilaku-bahasa-&quot;&gt;2. Translation Memory sebagai “Database Perilaku Bahasa”&lt;/h3&gt;
&lt;p&gt;Anggap TM bukan sekadar pasangan kalimat, tapi &lt;strong&gt;memori organisasi&lt;/strong&gt;: keputusan terminologi, gaya, dan konteks yang bisa diwariskan ke proyek berikutnya.&lt;/p&gt;
&lt;h4 id=&quot;apa-yang-harus-disimpan-di-tm-bukan-cuma-source-target-&quot;&gt;Apa yang harus disimpan di TM (bukan cuma source-target)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;segment_id&lt;/strong&gt;: ID stabil untuk pelacakan perubahan.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;source_text&lt;/strong&gt; + &lt;strong&gt;target_text&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;context&lt;/strong&gt;: halaman/fitur, siapa penggunanya, formal/informal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;glossary_terms&lt;/strong&gt;: istilah wajib (dan &lt;em&gt;forbidden terms&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;quality_meta&lt;/strong&gt;: siapa reviewer, status, tanggal, dan alasan perubahan.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;struktur-data-yang-ramah-git-&quot;&gt;Struktur data yang “ramah Git”&lt;/h4&gt;
&lt;p&gt;Kalau TM disimpan sebagai file yang bisa di-&lt;em&gt;diff&lt;/em&gt; (mis. JSON/YAML/CSV yang dibatasi aturan), kita dapat keuntungan besar: perubahan terlihat jelas, bisa di-review, bisa di-&lt;em&gt;rollback&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rekomendasi format praktis:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Format             &lt;/th&gt;&lt;th&gt;Pro                              &lt;/th&gt;&lt;th&gt;Kontra                                      &lt;/th&gt;&lt;th&gt;Cocok untuk&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;JSON Lines (jsonl) &lt;/td&gt;&lt;td&gt;mudah di-&lt;em&gt;stream&lt;/em&gt;, mudah diparse &lt;/td&gt;&lt;td&gt;diff bisa bising jika tidak distandardisasi &lt;/td&gt;&lt;td&gt;pipeline lint/QA otomatis&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;YAML               &lt;/td&gt;&lt;td&gt;manusiawi, komentar enak         &lt;/td&gt;&lt;td&gt;rawan variasi style                         &lt;/td&gt;&lt;td&gt;tim kecil yang sering review manual&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CSV/TSV            &lt;/td&gt;&lt;td&gt;simpel, kompatibel tooling lama  &lt;/td&gt;&lt;td&gt;konteks kompleks sulit                      &lt;/td&gt;&lt;td&gt;import/export cepat&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Untuk inspirasi pola “file-based knowledge yang versionable”, kamu bisa lihat bagaimana beberapa sistem modern menyimpan definisi dalam YAML dan mengontrol evolusinya lewat Git (contoh pendekatan &lt;em&gt;version-controllable YAML in Git&lt;/em&gt; dibahas dalam artikel Coderlegion: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/11575/74-ai-personas-one-architecture-how-we-built-axis-part1&quot;&gt;How We Built Axis (Part 1)&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&quot;3-versioning-dengan-git-menjadikan-bahasa-bisa-diaudit&quot;&gt;3. Versioning dengan Git: Menjadikan Bahasa Bisa Diaudit&lt;/h3&gt;
&lt;p&gt;Git bukan cuma buat kode. Untuk penerjemahan, Git memberi tiga hal: &lt;strong&gt;jejak keputusan&lt;/strong&gt;, &lt;strong&gt;kolaborasi yang disiplin&lt;/strong&gt;, dan &lt;strong&gt;kemampuan mengembalikan kondisi&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;pola-repo-yang-enak-dipakai-tim-campuran-dev-linguist-&quot;&gt;Pola repo yang enak dipakai tim campuran (dev + linguist)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/tm/&lt;/code&gt; → translation memory (jsonl/yaml)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/glossary/&lt;/code&gt; → istilah baku (source term, target term, catatan)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/styleguide/&lt;/code&gt; → aturan gaya (sapaan, formalitas, angka, satuan)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/checks/&lt;/code&gt; → aturan QA (regex, lints, scripts)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;praktik-git-yang-bernyawa-untuk-penerjemahan&quot;&gt;Praktik Git yang “bernyawa” untuk penerjemahan&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Branch per batch&lt;/strong&gt; (mis. &lt;code&gt;loc/checkout-flow-id&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PR template&lt;/strong&gt; untuk reviewer bahasa:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;scope perubahan&lt;/li&gt;
&lt;li&gt;istilah baru&lt;/li&gt;
&lt;li&gt;risiko (UI overflow, legal wording)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Conventional commits&lt;/strong&gt; untuk lokalisasi:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;loc:&lt;/code&gt; perubahan terjemahan&lt;/li&gt;
&lt;li&gt;&lt;code&gt;glossary:&lt;/code&gt; istilah&lt;/li&gt;
&lt;li&gt;&lt;code&gt;qa:&lt;/code&gt; aturan pengecekan&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Di sinilah keyword &lt;em&gt;translation memory qa git&lt;/em&gt; terasa bukan jargon—tapi sebuah sistem kerja.&lt;/p&gt;
&lt;h3 id=&quot;4-qa-otomatis-linter-untuk-bahasa-bukan-cuma-kode&quot;&gt;4. QA Otomatis: Linter untuk Bahasa, Bukan Cuma Kode&lt;/h3&gt;
&lt;p&gt;Sebelum membuat QA otomatis, tentukan dulu “bug bahasa” yang ingin dicegah. Jangan mulai dari 200 aturan; mulai dari 10 yang paling sering bikin masalah.&lt;/p&gt;
&lt;h4 id=&quot;checklist-qa-yang-biasanya-berdampak-besar&quot;&gt;Checklist QA yang biasanya berdampak besar&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Terminologi wajib&lt;/strong&gt; (glossary enforcement)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Angka, mata uang, satuan&lt;/strong&gt; konsisten&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Placeholder aman&lt;/strong&gt; (mis. &lt;code&gt;{name}&lt;/code&gt;, &lt;code&gt;%s&lt;/code&gt;, &lt;code&gt;{{count}}&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Panjang teks&lt;/strong&gt; untuk UI tertentu (guardrail)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forbidden words&lt;/strong&gt; (mis. istilah yang tidak sesuai brand)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;contoh-aturan-sederhana-yang-sering-menyelamatkan-&quot;&gt;Contoh aturan sederhana (yang sering menyelamatkan)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Placeholder harus sama di source dan target&lt;/li&gt;
&lt;li&gt;Dilarang menerjemahkan nama fitur tertentu&lt;/li&gt;
&lt;li&gt;Format tanggal konsisten (mis. &lt;code&gt;YYYY-MM-DD&lt;/code&gt; untuk log, &lt;code&gt;25 Februari 2026&lt;/code&gt; untuk copy)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;tabel-bug-bahasa-vs-mekanisme-pencegahan&quot;&gt;Tabel: bug bahasa vs mekanisme pencegahan&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Jenis bug                &lt;/th&gt;&lt;th&gt;Contoh               &lt;/th&gt;&lt;th&gt;Dampak               &lt;/th&gt;&lt;th&gt;Guardrail QA&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Placeholder rusak        &lt;/td&gt;&lt;td&gt;&lt;code&gt;{count}&lt;/code&gt; hilang     &lt;/td&gt;&lt;td&gt;crash / string kacau &lt;/td&gt;&lt;td&gt;placeholder parity check&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Istilah liar             &lt;/td&gt;&lt;td&gt;“Apply” → “Aplikasi” &lt;/td&gt;&lt;td&gt;salah makna          &lt;/td&gt;&lt;td&gt;glossary enforcement&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Konsistensi angka        &lt;/td&gt;&lt;td&gt;1.000 vs 1000        &lt;/td&gt;&lt;td&gt;trust turun          &lt;/td&gt;&lt;td&gt;numeric normalization&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Register tidak konsisten &lt;/td&gt;&lt;td&gt;kamu/Anda campur     &lt;/td&gt;&lt;td&gt;brand tidak rapi     &lt;/td&gt;&lt;td&gt;styleguide lint&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;5-howto-implementasi-translation-memory-qa-git-dalam-7-langkah&quot;&gt;5. HowTo: Implementasi “translation memory qa git” dalam 7 Langkah&lt;/h3&gt;
&lt;p&gt;Bab ini sengaja dibuat &lt;em&gt;actionable&lt;/em&gt;. Anggap ini seperti &lt;em&gt;migration plan&lt;/em&gt; dari “terjemahan manual” menuju workflow yang bisa di-&lt;em&gt;ship&lt;/em&gt;.&lt;/p&gt;
&lt;h4 id=&quot;langkah-1-mulai-dari-inventaris-string&quot;&gt;Langkah 1 — Mulai dari inventaris string&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Tarik semua string (UI copy, docs, email template).&lt;/li&gt;
&lt;li&gt;Beri &lt;strong&gt;ID stabil&lt;/strong&gt; (jangan pakai kalimat sebagai ID).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;langkah-2-buat-glossary-minimalis&quot;&gt;Langkah 2 — Buat glossary minimalis&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;30 istilah terpenting dulu.&lt;/li&gt;
&lt;li&gt;Sertakan &lt;em&gt;do/don’t&lt;/em&gt; dan contoh konteks.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;langkah-3-pilih-format-tm-yang-bisa-didiff&quot;&gt;Langkah 3 — Pilih format TM yang bisa didiff&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Pilih &lt;code&gt;jsonl&lt;/code&gt; atau &lt;code&gt;yaml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Standardisasi urutan field dan formatting.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;langkah-4-terapkan-pr-workflow&quot;&gt;Langkah 4 — Terapkan PR workflow&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Reviewer bahasa masuk sebagai &lt;em&gt;code owner&lt;/em&gt; untuk folder tertentu.&lt;/li&gt;
&lt;li&gt;Gunakan PR template khusus lokalisasi.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;langkah-5-tambahkan-qa-script-paling-krusial&quot;&gt;Langkah 5 — Tambahkan QA script paling krusial&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Placeholder parity&lt;/li&gt;
&lt;li&gt;Glossary enforcement&lt;/li&gt;
&lt;li&gt;Basic punctuation/spacing rules&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;langkah-6-masukkan-ke-ci&quot;&gt;Langkah 6 — Masukkan ke CI&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Jalankan checks pada setiap PR.&lt;/li&gt;
&lt;li&gt;Gagal = PR tidak boleh merge.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;langkah-7-buat-release-notes-untuk-bahasa&quot;&gt;Langkah 7 — Buat “release notes” untuk bahasa&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Perubahan istilah besar perlu diumumkan.&lt;/li&gt;
&lt;li&gt;Catat alasan, bukan hanya hasil.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Skema ringkas (biar kebayang alurnya):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;String Inventory → TM/Glossary Repo → PR Review → QA Lint → CI Pass → Merge → Release&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Kalau kamu mengerjakan aplikasi multilingual, artikel Coderlegion lain yang relevan untuk konteks i18n adalah: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/4838/why-you-should-consider-intlayer-to-make-your-app-multilingual&quot;&gt;Why you should consider Intlayer to make your app multilingual&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;6-praktik-nyata-studi-kasus-mini-jepang-indonesia-&quot;&gt;6. Praktik Nyata: Studi Kasus Mini (Jepang–Indonesia)&lt;/h3&gt;
&lt;p&gt;Kami sering menemukan pola yang sama pada proyek bahasa Jepang–Indonesia: istilah teknis industri, SOP, dan nuansa formalitas yang ketat. Satu kata yang “hampir benar” bisa mengubah makna instruksi kerja.&lt;/p&gt;
&lt;h4 id=&quot;contoh-masalah&quot;&gt;Contoh masalah&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;「(&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=11776215105575912404&quot;&gt;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=11776215105575912404&lt;/a&gt;)」 diterjemahkan selalu “konfirmasi” padahal konteksnya bisa “periksa/cek/verifikasi”.&lt;/li&gt;
&lt;li&gt;Kata “mohon” vs “harap” memengaruhi &lt;em&gt;tone&lt;/em&gt;—terutama di dokumen SOP.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;cara-menguncinya-lewat-tm-git&quot;&gt;Cara menguncinya lewat TM + Git&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Simpan keputusan terjemahan per konteks.&lt;/li&gt;
&lt;li&gt;Tag segment dengan domain: &lt;code&gt;manufacturing&lt;/code&gt;, &lt;code&gt;hr&lt;/code&gt;, &lt;code&gt;training&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set reviewer berbeda untuk domain berbeda.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Di tahap ini, &lt;em&gt;translation memory qa git&lt;/em&gt; bukan sekadar “alat”, tapi budaya: semua keputusan punya jejak.&lt;/p&gt;
&lt;h3 id=&quot;7-faq&quot;&gt;7. FAQ&lt;/h3&gt;
&lt;h4 id=&quot;apakah-translation-memory-sama-dengan-mesin-terjemahan-mt-&quot;&gt;Apakah translation memory sama dengan mesin terjemahan (MT)?&lt;/h4&gt;
&lt;p&gt;Tidak. TM menyimpan pasangan segmen yang pernah diterjemahkan, lalu menyarankan ulang ketika ada kemiripan. MT menghasilkan terjemahan baru. Kombinasi keduanya boleh, tapi TM memberi konsistensi yang lebih bisa diaudit.&lt;/p&gt;
&lt;h4 id=&quot;kenapa-harus-pakai-git-kenapa-bukan-spreadsheet-saja-&quot;&gt;Kenapa harus pakai Git? Kenapa bukan spreadsheet saja?&lt;/h4&gt;
&lt;p&gt;Spreadsheet enak untuk start, tapi Git unggul untuk &lt;em&gt;diff&lt;/em&gt;, review berbasis PR, dan rollback. Begitu string bertambah dan tim membesar, audit trail Git biasanya lebih “jujur”.&lt;/p&gt;
&lt;h4 id=&quot;bagaimana-mencegah-repo-tm-jadi-berantakan-&quot;&gt;Bagaimana mencegah repo TM jadi berantakan?&lt;/h4&gt;
&lt;p&gt;Tetapkan format, pakai linter/formatter, dan buat aturan commit. Jangan izinkan perubahan manual tanpa PR.&lt;/p&gt;
&lt;h4 id=&quot;qa-otomatis-itu-berarti-tidak-butuh-proofreader-&quot;&gt;QA otomatis itu berarti tidak butuh proofreader?&lt;/h4&gt;
&lt;p&gt;Bukan. QA otomatis menangkap pola bug (placeholder, angka, istilah), sedangkan proofreader menjaga naturalness dan konteks. Keduanya saling menguatkan.&lt;/p&gt;
&lt;h4 id=&quot;apa-langkah-pertama-paling-ringan-untuk-tim-kecil-&quot;&gt;Apa langkah pertama paling ringan untuk tim kecil?&lt;/h4&gt;
&lt;p&gt;Mulai dari glossary kecil + placeholder checks. Setelah terasa manfaatnya, baru tumbuhkan ke TM dan CI.&lt;/p&gt;
&lt;h3 id=&quot;closing-bahasa-yang-bisa-dipelihara-seperti-kode&quot;&gt;Closing: Bahasa yang Bisa Dipelihara Seperti Kode&lt;/h3&gt;
&lt;p&gt;Sebagai penutup, kita bisa meminjam satu prinsip yang sangat “engineering”: &lt;em&gt;software&lt;/em&gt; terbaik adalah yang mudah dipahami manusia—dan itu berlaku juga untuk terjemahan. Martin Fowler pernah menulis, “Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” (lihat sumbernya di &lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikiquote.org/wiki/Martin_Fowler&quot;&gt;Wikiquote Martin Fowler&lt;/a&gt;). &lt;strong&gt;Artinya:&lt;/strong&gt; “Siapa pun bisa menulis kode yang dimengerti komputer. Programmer yang baik menulis kode yang dimengerti manusia.” Fowler adalah software engineer dan penulis berpengaruh di ranah &lt;em&gt;refactoring&lt;/em&gt; dan praktik modern seperti CI; kutipannya pas karena TM + QA + Git pada dasarnya membuat bahasa menjadi sesuatu yang &lt;em&gt;readable&lt;/em&gt;, &lt;em&gt;maintainable&lt;/em&gt;, dan tidak mudah menipu kita dengan “kelihatannya benar”.&lt;/p&gt;
&lt;p&gt;Kalau kamu mau mulai membangun pipeline yang rapi, ingat: jangan buru-buru menambah aturan; mulai dari bug yang paling sering muncul, dan jadikan bahasa bagian dari quality gate. Dari sana, workflow &lt;strong&gt;translation memory qa git&lt;/strong&gt; akan terasa natural—seperti menambahkan unit test pada modul yang dulu rawan error.&lt;/p&gt;
&lt;p&gt;Untuk konteks praktik bahasa Jepang–Indonesia (kelas bahasa, corporate training, dan layanan penerjemahan) yang sering bersinggungan dengan dokumen industri dan kebutuhan profesional, kamu bisa lihat referensi dan program kami di &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.kursusbahasajepang.co.id/&quot;&gt;kursus bahasa Jepang Tensai Indonesia&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;@context&quot;: &quot;https://schema.org&quot;,
  &quot;@type&quot;: &quot;BlogPosting&quot;,
  &quot;headline&quot;: &quot;Translation Memory untuk Tim Penerjemahan: Desain Data, Versioning dengan Git, dan QA Otomatis (Praktik Nyata)&quot;,
  &quot;description&quot;: &quot;Panduan praktis membangun workflow translation memory, QA otomatis, dan versioning Git untuk menjaga konsistensi terjemahan di tim modern.&quot;,
  &quot;inLanguage&quot;: &quot;id-ID&quot;,
  &quot;keywords&quot;: [
    &quot;translation memory qa git&quot;,
    &quot;translation memory&quot;,
    &quot;quality assurance&quot;,
    &quot;git workflow&quot;,
    &quot;lokalisasi&quot;
  ],
  &quot;author&quot;: {
    &quot;@type&quot;: &quot;Organization&quot;,
    &quot;name&quot;: &quot;Tensai Indonesia&quot;,
    &quot;url&quot;: &quot;https://www.kursusbahasajepang.co.id/&quot;
  },
  &quot;publisher&quot;: {
    &quot;@type&quot;: &quot;Organization&quot;,
    &quot;name&quot;: &quot;Coderlegion&quot;,
    &quot;url&quot;: &quot;https://coderlegion.com&quot;
  },
  &quot;isBasedOn&quot;: [
    &quot;https://about.gitlab.com/blog/how-we-built-and-automated-our-new-japanese-gitlab-docs-site/&quot;,
    &quot;https://aclanthology.org/2022.eurali-1.6.pdf&quot;
  ],
  &quot;mainEntityOfPage&quot;: {
    &quot;@type&quot;: &quot;WebPage&quot;,
    &quot;url&quot;: &quot;https://coderlegion.com/&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12156/translation-memory-untuk-tim-penerjemahan-desain-data-versioning-dengan-git-dan-otomatis</guid>
<pubDate>Tue, 10 Mar 2026 17:00:00 +0000</pubDate>
</item>
</channel>
</rss>