Soroush Neyestani

Application Developer

Software Developer

Data Scientist

Project Manager

Senior Project Manager

IT Administrator

Fullstack Developer

Android Developer

iOS Developer

Persian-to-Braille CLI

From a 2016 Macro-Based Converter to a Specification-Driven Translation Engine

I originally created Persian-to-Braille in 2016.

The first version belonged to a very different software environment. Translation behavior was distributed across Microsoft Office macros, database-backed mappings, conversion tables, and application-specific workflows.

For its original purpose, that architecture worked.

A Persian character could be looked up, mapped to a Braille representation, and converted inside the Office environment in which the software was being used.

Ten years later, however, I was no longer interested in simply updating the old macros.

I wanted to answer a more fundamental engineering question:

How should Persian Braille translation be designed if the rules must exist independently of the application that executes them?

That question became the foundation of the 2026 rebuild.

The first complete application built on the new architecture is the Persian-to-Braille command-line interface.


The Problem with the Original Architecture

The 2016 implementation can be simplified as:

Persian text
    ↓
Database mappings
    ↓
Office / VBA logic
    ↓
Braille output

This architecture was practical, but the translation model and the application were tightly coupled.

That created several long-term problems.

What was the authoritative Persian Braille rule?

Was a database record a formal rule, or simply historical implementation data?

What happened when Word VBA, Excel VBA, and SQL contained different mappings?

How should Unicode edge cases behave?

Could another application use the translator without reproducing the old Office logic?

Could translation behavior be tested independently from Microsoft Office?

These were not primarily performance problems.

They were architecture, ownership, and specification problems.


The 2026 Architecture

The rebuilt system separates those responsibilities explicitly:

Canonical Specification
        ↓
Translation Core
        ↓
Public TypeScript SDK
        ↓
CLI

The architectural principle is:

The specification owns the rules.
The Core executes them.
The SDK exposes them.
The CLI consumes them.

This means the CLI contains no independent Persian Braille mapping table.

It does not decide how a Persian character should be represented in Braille.

It does not define normalization behavior.

It does not determine rule precedence.

It does not create its own fallback behavior for unsupported input.

The CLI is deliberately only a consumer boundary.

That distinction is central to the entire redesign.


From Mapping Tables to an Executable Specification

The legacy project was largely built around mappings:

Persian character → Braille representation

The modern project treats mapping as only one part of a larger translation system.

The executable profile at this milestone is:

id: fa-ir-g1
version: 0.1.0
status: draft
direction: print-to-braille

At the CLI milestone, the runtime specification contained:

175 admitted rules
37 normative rules
138 candidate rules

Those rules are not limited to simple one-character replacements.

The translation model also includes concepts such as:

  • character rules

  • sequences

  • context-sensitive behavior

  • rule precedence

  • layout behavior

  • modes

  • normalization behavior

  • explicit failure states

This is an important architectural shift.

A language translation system — even a specialized Braille translator — cannot always be modeled safely as a dictionary lookup.


Unicode as Part of the Contract

Persian text processing requires explicit Unicode handling.

The rebuilt engine preserves both:

code-point index
UTF-16 index

when reporting relevant locations and failures.

Known Unicode format controls are handled deliberately.

Unknown format controls fail closed rather than disappearing silently.

This matters because a translation system should not quietly modify an input simply because a particular Unicode value was inconvenient for the implementation.

Unsupported or ambiguous states are part of the public translation contract.


Deterministic Translation Core

The Core executes runtime data generated from the canonical specification.

It does not maintain another handwritten copy of the same mappings.

Rule selection considers:

  • context eligibility

  • sequence matching

  • precedence

  • character fallback

  • structural behavior

  • normalization behavior

  • explicit failure conditions

The same input under the same profile should produce the same public result.

When the engine cannot translate an input according to the admitted rules, it does not silently guess.

It fails explicitly.

Current public failure categories include:

PREPROCESSING_FAILED
UNKNOWN_CHARACTER
UNKNOWN_SEQUENCE
AMBIGUOUS_MATCH
UNSUPPORTED_ENGINE_STATE

Failure is not treated as an implementation accident.

Failure semantics are part of the API.


A Real Command-Line Interface

The executable is:

persian-braille

Translation can be performed directly:

persian-braille translate "سلام"

or from standard input:

persian-braille translate --stdin

The bundled profile can also be inspected:

persian-braille profile

The CLI provides three primary output representations:

unicode
cells
json

For example:

persian-braille translate "سلام"

returns Unicode Braille.

For Braille cell notation:

persian-braille translate "سلام" --format cells

For software integration:

persian-braille translate "سلام" --format json

The JSON representation is deterministic and machine-readable, making the CLI useful as more than an interactive utility.

It can act as an automation boundary for scripts, testing tools, pipelines, and future integrations.


Stable Process Semantics

A CLI intended for automation needs more than readable output.

It needs a predictable process contract.

The current exit codes distinguish between:

0  success
1  expected translation failure
2  usage or input-contract error
3  unexpected internal failure

An unsupported translation input is therefore not treated as the same type of failure as invalid CLI usage.

And neither is confused with an unexpected software defect.

Human-readable execution and machine-readable execution are handled separately as well.

Expected failures can be reported clearly to a terminal user while JSON consumers still receive structured data they can reliably process.


Input Is Not Silently Rewritten

One deceptively small design decision was preserving input received through stdin.

The CLI does not automatically trim translation input.

Whitespace and line boundaries may have structural meaning.

An application should therefore not modify the translation language before the Core receives it unless the specification explicitly defines that transformation.

This reflects the broader rule of the architecture:

Consumers do not own translation semantics.


Determinism and Reproducibility

Repeated JSON translation of the same input is expected to produce byte-identical output.

That requirement improves:

  • regression testing

  • automation

  • snapshots

  • reproducible pipelines

  • debugging

  • future third-party integration

The CLI deliberately avoids introducing irrelevant runtime state, such as timestamps, into deterministic translation envelopes.

The repository also validates clean-state dependency construction:

Core
 ↓
SDK
 ↓
CLI

A successful build should not depend on a stale dist directory left behind from yesterday.

Reproducibility has to work from a clean workspace.


Verification at the CLI Milestone

At this stage of the project, the validation baseline included:

Core tests:         210 / 210
SDK tests:           12 / 12
CLI tests:           13 / 13
Conformance tests:   22 / 22
Profile scenarios:   15 / 15

Repository-level validation also covered:

  • Unicode normalization

  • architectural dependency boundaries

  • runtime-specification consumption

  • specification determinism

  • SDK packaging

  • repository hygiene

  • clean-state reproducibility

  • GitHub CI

The question is no longer merely:

Does the macro appear to produce the expected output?

It is now:

Can the complete translation architecture reproduce its defined behavior from a clean state through a controlled public interface?


Legacy Sources as Evidence

The modern project does not pretend that the 2016 version never existed.

The opposite is true.

Historical Word VBA, Excel VBA, and SQL implementations were extracted and compared as evidence.

Agreement between legacy sources is useful evidence.

Disagreement is recorded as disagreement.

But historical implementation behavior is not automatically promoted into a formal standard.

That creates an important separation:

Legacy behavior is evidence.
The specification is authority.

This allows the project to preserve its history without allowing historical implementation accidents to silently become permanent language rules.


Why Start with a CLI?

A CLI is an effective architectural test.

There is very little interface complexity behind which business logic can hide.

It forces a software system to answer clearly:

  • What is the input?

  • What is the output?

  • What constitutes failure?

  • What goes to stdout?

  • What goes to stderr?

  • What is the exit status?

  • Can another program automate it?

  • Can the application use the engine without accessing its internals?

If a translation system can expose a clean command-line interface while keeping its internal rule engine private, its reusable boundary is beginning to become real.


Ten Years of Architectural Change

The visible transition is simple:

2016
Database + Macros

        ↓

2026
CLI

The actual transition is much larger:

2016

Database mappings
      ↓
Office macros
      ↓
Application-owned translation


2026

Canonical specification
      ↓
Unicode-aware Core
      ↓
Public SDK
      ↓
CLI

In 2016, the macros and database were effectively the project.

In 2026, they became historical implementation sources.

The translation rules became specification data.

The converter became a deterministic Core.

The reusable software boundary became a public SDK.

And the first complete executable consumer became a CLI.

That is the modernization I wanted from this project.

Not:

old interface → new interface

but:

implementation-coupled translation → application-independent translation architecture


Repository

GitHub:
https://github.com/soroushneyestani/Persian-to-Braille

Persian-to-Braille is an open-source project developed around accessibility, Persian language processing, Unicode correctness, deterministic software behavior, and reusable developer tooling.