Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
youssefsiam38 committed Aug 9, 2024
1 parent f8db41e commit 0cc74a3
Show file tree
Hide file tree
Showing 9 changed files with 961 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dictionaries
.fasi7*
99 changes: 99 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Fasi7

## Overview

Fasi7 is a command-line interface (CLI) tool designed to help developers translate their localization files using AI. It automates the process of translating content from one language to multiple target languages, preserving placeholders and adapting to the context of your business.

## Installation

Download the latest release from the [releases page](https://github.com/youssefsiam38/fasi7/releases)

## Configuration

Fasi7 uses a configuration file to store settings. By default, it looks for a file named `.fasi7.yaml` in the current directory.

### Creating a Configuration File

To create a new configuration file, run:

```
fasi7 init
```

### Configuration Options

The following options can be set in the configuration file:

- `openai.apiKey`: Your OpenAI API key
- `openai.model`: The OpenAI model to use for translations
- `openai.systemPrompt`: Custom system prompt for the AI (optional)
- `outputLocales`: List of target locales for translation
- `inputLocale`: The source locale to translate from
- `businessDescription`: A description of your business to provide context for translations
- `dir`: The directory containing localization files
- `ignoreFilesWithContent`: (Optional) If specified, files containing this string will be ignored

## Usage

To run Fasi7 and translate your localization files:

```
fasi7
```

This command will:

1. Read the configuration file
2. Recursively search for files in the specified directory
3. Identify files matching the input locale
4. Translate the content to each specified output locale
5. Write the translated content to new files, preserving the directory structure

## Command-line Options

- `--config`: Specify a custom path for the configuration file (default: `.fasi7.yaml`)

Example:
```
fasi7 --config /path/to/custom/config.yaml
```

## How It Works

1. Fasi7 uses the OpenAI API to perform translations.
2. It sends each file's content to the AI along with a system prompt that includes:
- The source and target languages
- Instructions to preserve placeholders
- Your business description for context
3. The AI generates a translation, which is then saved to a new file in the appropriate locale directory.

## Error Handling

Fasi7 will exit with an error message in the following cases:

- No configuration file found
- Missing required configuration options
- File read/write errors
- OpenAI API errors

## Concurrent Processing

Fasi7 translates files concurrently to improve performance when dealing with multiple output locales or many files.

## Best Practices

1. Provide a detailed business description in your configuration to improve translation accuracy and context.
2. Use meaningful placeholder names in your localization files to ensure they are correctly preserved.
3. Choose an appropriate model for your needs.

## Limitations

- Fasi7 relies on the OpenAI API, which may have usage limits and costs associated with it.
- The quality of translations depends on the AI model used and the context provided.

## Contributing

Contributions are welcome! Please submit issues or pull requests to help improve Fasi7.

## License
MIT License
22 changes: 22 additions & 0 deletions cmd/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package cmd

import (
"github.com/spf13/cobra"
"github.com/youssefsiam38/fasi7/utils"
)

func init() {
rootCmd.AddCommand(initCmd)
}

var initCmd = &cobra.Command{
Use: "init",
Short: "Initialize Fasi7",
Long: `Initialize Fasi7 by creating a config file`,
Run: func(cmd *cobra.Command, args []string) {
err := utils.WriteFile(".fasi7.yaml", []byte("dir: ./locales # Required\r\nbusinessDescription: | # This will be ignored if you specify a systemPrompt\r\n <Business description here>\r\nopenai:\r\n apiKey: ${OPENAI_API_KEY} # Required\r\n model: gpt-4o-mini # Required\r\n # systemPrompt: | # Optional\r\ninputLocale: en # Required\r\noutputLocales: # Required\r\n - ar\r\n - de\r\n - es\r\n - fr\r\n - ru\r\nignoreFilesWithContent: '{}' # Optional"))
if err != nil {
panic(err)
}
},
}
138 changes: 138 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package cmd

import (
"fmt"
"os"
"strings"
"sync"

"github.com/sashabaranov/go-openai"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/youssefsiam38/fasi7/utils"
)

var cfgFile string
var noConfig bool

func init() {
cobra.OnInitialize(initConfig)
// config
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", ".fasi7.yaml", "config file path")
}

func initConfig() {
viper.SetConfigFile(cfgFile)
if err := viper.ReadInConfig(); err != nil {
noConfig = true
}
}

var rootCmd = &cobra.Command{
Use: "fasi7",
Short: "Fasi7 is a localization files translation tool using AI",
Long: `Fasi7 was made to help developers to translate their localization files using AI. for more information visit github.com/youssefsiam38/fasi7`,
Run: func(cmd *cobra.Command, args []string) {
if noConfig {
fmt.Println("No config file found, run 'fasi7 init' to create one")
os.Exit(1)
}
ctx := cmd.Context()
openaiClient := openai.NewClient(utils.GetConfigString("openai.apiKey"))
outputLocales := viper.GetStringSlice("outputLocales")
if len(outputLocales) == 0 {
fmt.Println("No output locales found in config file")
os.Exit(1)
}

inputLocale := utils.GetConfigString("inputLocale")
if inputLocale == "" {
fmt.Println("No input locale found in config file")
os.Exit(1)
}

businessDescription := utils.GetConfigString("businessDescription")
if businessDescription == "" {
fmt.Println("No business description found in config file")
os.Exit(1)
}

files, err := utils.GetFilesRecursive(utils.GetConfigString("dir"))
if err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}

wg := sync.WaitGroup{}

for _, file := range files {
if !strings.Contains(file, inputLocale) {
continue
}

fileContent, err := os.ReadFile(file)
if err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}

if utils.GetConfigString("ignoreFilesWithContent") != "" && strings.Contains(string(fileContent), utils.GetConfigString("ignoreFilesWithContent")) {
continue
}

for _, outputLocale := range outputLocales {
wg.Add(1)
go func(fileContent []byte, file, outputLocale string) {
defer wg.Done()
systemPrompt := utils.GetConfigString("openai.systemPrompt")
if systemPrompt == "" {
systemPrompt = fmt.Sprintf(`
You are an expert language translator. Your task is to translate the text provided to you from %s to %s, while preserving any placeholders that may be present in the source text.
The input text will be the original file content, and the output text should be parsable content, do not include any comments or unnecessary whitespace.
If the input text contains any placeholders (e.g. {name}, {count}, etc.), ensure that these placeholders remain unchanged in the Arabic translation.
Provide a high-quality translation that conveys the original meaning accurately and idiomatically. Do not simply perform a literal word-for-word translation, but adapt the phrasing and grammar to produce natural-sounding text that is appropriate for the target audience for the business described below.
Business Description:
%s
`, utils.IsoToLanguage(inputLocale), utils.IsoToLanguage(outputLocale), businessDescription)
}

res, err := openaiClient.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: utils.GetConfigString("openai.model"),
Messages: []openai.ChatCompletionMessage{
{
Role: "system",
Content: systemPrompt,
},
{
Role: "user",
Content: string(fileContent),
},
},
})
if err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}
err = utils.WriteFile(strings.ReplaceAll(file, fmt.Sprintf("/%s/", inputLocale), fmt.Sprintf("/%s/", outputLocale)), []byte(res.Choices[0].Message.Content))
if err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}
}(fileContent, file, outputLocale)
}
}

wg.Wait()
},
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
20 changes: 20 additions & 0 deletions cmd/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package cmd

import (
"fmt"

"github.com/spf13/cobra"
)

func init() {
rootCmd.AddCommand(versionCmd)
}

var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of Fasi7",
Long: `All software has versions. This is Fasi7's`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Fasi7 v0.1")
},
}
33 changes: 33 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module github.com/youssefsiam38/fasi7

go 1.22.0

require (
github.com/samber/lo v1.46.0
github.com/sashabaranov/go-openai v1.28.1
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
)

require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.16.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading

0 comments on commit 0cc74a3

Please sign in to comment.