# Create a file table provider for text files

> Ship localization tables as plain text files with your own format.

Ship localization tables as plain text files with your own format.

The built-in data-file source ships tables as JSON. This example replaces the format with plain `key=value` text files that translators can edit in any text editor. Build-time file generation, runtime loading, and language drop-in still work.

A file format has two halves: a runtime reader that turns a file into table data, and a writer in the Unity Editor that generates the files. For how the generated files ship and load, refer to [Data file tables](/engine/6000.7/manual/localization/content-storage-and-loading/data-file-tables.md). The example's file looks like this:

```text
@collection=Menus
@guid=1f4a09e21b6c44dd8f2eab27d3a54c10
@locale=fr
# key=value, one entry per line
greeting=Bonjour
quit=Quitter
```

## Prerequisites

Before you start, make sure you have the following:

* A project with Localization settings and a table collection. Refer to [Get started with localization](/engine/6000.7/manual/get-started.md).

## Create the reader

The reader script parses a file's stream into a [`ResourceTableData`](/engine/6000.7/script-reference/unity/localization/providers/filetables/resourcetabledata.md) snapshot, which consists of the collection header plus one [`EntryData`](/engine/6000.7/script-reference/unity/localization/providers/filetables/entrydata.md) per row. The reader implements [`ITableFileReader`](/engine/6000.7/script-reference/unity/localization/providers/filetables/itablefilereader.md):

```cs
public class TextTableReader : ITableFileReader
    {
        public const string Extension = "txt";
        public static readonly TextTableReader Instance = new();

        public string FileExtension => Extension;

        public ResourceTableData Read(Stream stream)
        {
            var data = new ResourceTableData();
            using var reader = new StreamReader(stream, Encoding.UTF8, true, 1024, leaveOpen: true);
            while (reader.ReadLine() is { } line)
            {
                // Lines starting with '#' are comments; every other line is "key=value".
                if (line.StartsWith("#", StringComparison.Ordinal))
                    continue;
                var split = line.IndexOf('=');
                if (split <= 0)
                    continue;
                var key = line[..split].Trim();
                var value = line[(split + 1)..].Trim();
                switch (key)
                {
                    case "@collection":
                        data.CollectionName = value;
                        break;
                    case "@guid":
                        data.CollectionGuid = value;
                        break;
                    case "@locale":
                        data.LocaleCode = value;
                        break;
                    default:
                        // The file carries no entry ids, so the shared table data matches rows
                        // by key and assigns an id to any key it has not seen before.
                        data.Entries.Add(new EntryData { Key = key, Value = value });
                        break;
                }
            }
            return data;
        }
    }
```

## Create the provider

The provider script is the content source itself. The class derives from [`FileTableProvider`](/engine/6000.7/script-reference/unity/localization/providers/filetables/filetableprovider.md) and returns the reader. The base class handles file lookup, locale discovery, and rebuilding tables.

```cs
[Serializable]
    public class TextResourceProvider : FileTableProvider
    {
        protected override ITableFileReader Reader => TextTableReader.Instance;
    }
```

## Create the writer

The writer script performs the opposite operation to the reader script. It turns a `ResourceTableData` snapshot into file content. The writer script runs in the Editor, so put it in an `Editor` folder.

```cs
public class TextTableWriter : ITableFileWriter
    {
        public static readonly TextTableWriter Instance = new();

        public void Write(ResourceTableData data, Stream stream)
        {
            using var writer = new StreamWriter(stream, new UTF8Encoding(false), 1024, leaveOpen: true);
            writer.WriteLine($"@collection={data.CollectionName}");
            writer.WriteLine($"@guid={data.CollectionGuid}");
            writer.WriteLine($"@locale={data.LocaleCode}");
            foreach (var entry in data.Entries)
            {
                if (entry != null && !string.IsNullOrEmpty(entry.Key))
                    writer.WriteLine($"{entry.Key}={entry.Value}");
            }
        }
    }
```

## Register the Editor half

Connect the writer to the provider type with an Editor class. Without a registered Editor, Unity never generates the provider's files. This script runs in the Editor, so put it in an `Editor` folder.

```cs
// Registering an editor for the provider type is what makes Unity generate its files at build time.
    [AssetProviderEditor(typeof(TextResourceProvider))]
    public class TextResourceProviderEditor : FileTableProviderEditor
    {
        public override ITableFileWriter Writer => TextTableWriter.Instance;
    }
```

## Use the new source

To use your provider in a project:

1. Open **Edit** > Editor > **Localization** and add **Text Resource Provider** to the content sources.
2. Select a table collection asset and assign it to the new source in the Editor window.
3. To test without building, set the source's **Play Mode Source** to **Generated files** and enter Play mode: values now come from generated `.txt` files.
4. Build the Player and check the output: `StreamingAssets/LocalizationTables` contains one `.txt` file per locale.

## Add a language by file

Because `FileTableProvider` discovers locales from files, the format supports post-release languages without extra work. To add a new language, copy an existing locale's `.txt` file, rename it to the new locale code, translate it, and relaunch. Refer to [Add a language to a built Player](/engine/6000.7/manual/localization/content-storage-and-loading/add-a-language-to-a-built-player.md).

## Additional resources

* [Data file tables](/engine/6000.7/manual/localization/content-storage-and-loading/data-file-tables.md)
* [Add a language to a built Player](/engine/6000.7/manual/localization/content-storage-and-loading/add-a-language-to-a-built-player.md)
* [Content sources](/engine/6000.7/manual/localization/content-storage-and-loading/content-sources.md)
