# How the Text Expander Reads Custom Expansions from `~/.you_expand_me.json` in hallelujahIM

> Discover how hallelujahIM's Text Expander loads custom abbreviations from ~/.you_expand_me.json at startup. Learn to store and apply personalized expansions efficiently.

- Repository: [dongyuwei/hallelujahim](https://github.com/dongyuwei/hallelujahim)
- Tags: internals
- Published: 2026-02-28

---

**The hallelujahIM input method loads user-defined abbreviations from `~/.you_expand_me.json` at startup via `deserializeJSON`, stores them in `self.substitutions`, and inserts matching expansions as priority candidates during the `getCandidates:` pipeline.**

The **hallelujahIM** input method for macOS provides a powerful **Text Expander** feature that lets you define custom abbreviations in a JSON configuration file. Located in the `dongyuwei/hallelujahIM` repository, this functionality allows you to type short codes like `"yem"` and instantly receive full phrases like `"you expand me"` as suggestion candidates. The implementation resides primarily in `src/ConversionEngine.mm` and operates through a straightforward JSON deserialization and dictionary lookup mechanism.

## How the Text Expander Loads Custom Expansions

### Locating the Configuration File

When the input method engine initializes, it looks for a file named [`.you_expand_me.json`](https://github.com/dongyuwei/hallelujahim/blob/main/.you_expand_me.json) in the user's home directory. The `getUserDefinedSubstitutions` method constructs the absolute path using `NSHomeDirectory()` and returns the deserialized dictionary:

```objc
- (NSDictionary *)getUserDefinedSubstitutions {
    NSString *path = [NSString stringWithFormat:@"%@%@", NSHomeDirectory(),
                      @"/.you_expand_me.json"];
    return deserializeJSON(path);
}

```

*Source: `src/ConversionEngine.mm`, lines 61–64.*

This method returns an `NSDictionary` containing the user-defined abbreviation mappings.

### Storing the Substitution Dictionary

During the asynchronous data-initialization phase in `loadPreparedData`, the engine stores the loaded dictionary in an instance variable:

```objc
self.substitutions = [self getUserDefinedSubstitutions];

```

*Source: `loadPreparedData`, lines 30–34.*

This ensures the configuration remains available in memory throughout the IME session, eliminating redundant file I/O during typing.

## Applying Expansions During Candidate Generation

When you type a buffer, the engine calls `getCandidates:` to generate suggestions. After converting the input to lowercase, it performs a dictionary lookup against `self.substitutions`. If the typed string exists as a key, the corresponding value is injected at the front of the candidate list:

```objc
if (self.substitutions && self.substitutions[buffer]) {
    [result addObject:self.substitutions[buffer]];
}

```

*Source: `src/ConversionEngine.mm`, lines 86–89.*

The lowercasing ensures case-insensitive matching—for example, typing `"YEM"` or `"yem"` both match the `"yem"` key in your JSON file. After adding the custom expansion, the engine proceeds with its normal auto-suggestion, spell-check, and pinyin lookup pipelines, returning a deduplicated array of candidates.

## Configuration File Format

The `~/.you_expand_me.json` file must contain a flat JSON object where keys represent abbreviations and values represent the full expansion text:

```json
{
  "te": "text expander",
  "yem": "you expand me",
  "brb": "be right back"
}

```

Place this file in your home directory (`$HOME/.you_expand_me.json`). The keys should be lowercase to match the engine's lowercasing logic, though the values can contain any Unicode text including spaces and punctuation.

## Complete Implementation Flow

The following simplified flow illustrates how the three components work together:

```objc
// ① Load once at startup in ConversionEngine.mm
self.substitutions = [self getUserDefinedSubstitutions];

// ② During candidate generation when user types "yem"
NSString *buffer = originalInput.lowercaseString; // "yem"
if (self.substitutions && self.substitutions[buffer]) {
    // Adds "you expand me" to the front of suggestions
    [result addObject:self.substitutions[buffer]];
}

```

**End-user experience:** Type **`yem`** → the IME instantly suggests **`you expand me`** as the first candidate, allowing you to insert the full phrase with a single keystroke.

## Summary

- **Startup loading:** `getUserDefinedSubstitutions` reads `~/.you_expand_me.json` using `NSHomeDirectory()` and `deserializeJSON`.
- **Memory storage:** The JSON dictionary is cached in `self.substitutions` during `loadPreparedData`.
- **Real-time matching:** `getCandidates:` lowercases the input buffer and checks against the substitution dictionary, inserting matches at the top of the suggestion list.
- **Configuration:** A flat JSON file with abbreviation keys and expansion values controls the behavior.

## Frequently Asked Questions

### Where should I place the [`.you_expand_me.json`](https://github.com/dongyuwei/hallelujahim/blob/main/.you_expand_me.json) file?

Place the file in your macOS home directory at `~/.you_expand_me.json` (or `$HOME/.you_expand_me.json`). The engine specifically constructs this path using `NSHomeDirectory()` during initialization. The file will not be recognized if placed in subdirectories or renamed.

### Do I need to restart hallelujahIM after editing the JSON file?

Yes. As documented in the README, you must restart the input method after modifying `~/.you_expand_me.json`. The file is read only once during the `loadPreparedData` initialization phase; changes made while the IME is running require a restart to take effect.

### Is text expansion case-sensitive?

No. The engine converts your typed buffer to lowercase before checking against the substitution dictionary. This means typing `"YEM"`, `"Yem"`, or `"yem"` will all successfully match a key defined as `"yem"` in your JSON file. However, your JSON keys should be lowercase to ensure consistent behavior.

### What happens if my abbreviation matches multiple dictionary entries?

Each abbreviation key must be unique in the JSON file because the implementation uses a simple `NSDictionary` lookup. If you define duplicate keys, the JSON parser will retain only the last value encountered. The engine does not support multiple expansions for a single abbreviation; it returns exactly one match per key.