All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tlinx-gettext.kotlinx-gettext-plugin.0.5.1.source-code.PoFile.kt Maven / Gradle / Ivy

There is a newer version: 0.6.1
Show newest version
/*
 * Copyright 2022 Victor Kropp
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package name.kropp.kotlinx.gettext

import java.io.*

/**
 * Gettext .po (Portable Object) & .pot (PO Template) files support
 */
class PoFile(
    val entries: List,
    val header: PoEntry = DEFAULT_POT_HEADER
) {
    fun write(out: OutputStream) {
        PrintStream(out, false, Charsets.UTF_8).use { writer ->
            writer.println(header)
            for (message in entries) {
                writer.println()
                message.write(writer)
            }
        }
    }

    fun update(messages: List): PoFile {
        val grouped = messages.groupBy { it.text }.toMutableMap()
        val newEntries = entries.map { entry ->
            val updated = grouped.remove(entry.text)
            if (updated != null) {
                val newReferences = updated.flatMap { it.references }
                val updatedPaths = newReferences.map { it.substringBefore(':') }
                entry.copy(references = (entry.references.filterNot { updatedPaths.any { path -> it.startsWith(path)} } + newReferences).sortedWith(referenceComparator))
            } else {
                entry
            }
        } + grouped.map { group ->
            group.value.first().copy(references = group.value.flatMap { it.references }.sortedWith(referenceComparator))
        }
        return PoFile(newEntries, header)
    }

    companion object {
        @JvmStatic
        fun fromUnmerged(messages: List): PoFile {
            val merged =
                messages
                    .groupBy { it.text }
                    .map { group ->
                        group.value.first().copy(references = group.value.flatMap { it.references })
                    }
            return PoFile(merged)
        }

        @JvmStatic
        val referenceComparator = Comparator { r1, r2 ->
            val comparePaths = r1.substringBefore(':').compareTo(r2.substringBefore(':'))
            if (comparePaths != 0) {
                comparePaths
            } else {
                (r1.substringAfter(':').toIntOrNull() ?: 0).compareTo((r2.substringAfter(':').toIntOrNull() ?: 0))
            }
        }

        @JvmStatic
        fun read(input: InputStream): PoFile {
            val entries = mutableListOf()

            var comments = mutableListOf()
            var extractedComments = mutableListOf()
            var references = mutableListOf()
            var flags: String? = null
            var previous = mutableListOf()
            var context: String? = null
            var text: String? = null
            var plural: String? = null
            var cases = mutableListOf()

            input.bufferedReader(Charsets.UTF_8).useLines { lines ->
                lines.forEach { line ->
                    when {
                        line.isBlank() -> {
                            text?.let {
                                entries += PoEntry(comments, extractedComments, references, flags, previous, context, it, plural, cases)
                            }
                            comments = mutableListOf()
                            extractedComments = mutableListOf()
                            references = mutableListOf()
                            flags = null
                            previous = mutableListOf()
                            context = null
                            text = null
                            plural = null
                            cases = mutableListOf()
                        }
                        line == "#" -> comments += ""
                        line.startsWith("# ") -> comments += line.substringAfter("# ")
                        line.startsWith("#. ") -> extractedComments += line.substringAfter("#. ")
                        line.startsWith("#: ") -> references += line.substringAfter("#: ")
                        line.startsWith("#, ") -> flags = line.substringAfter("#, ")
                        line.startsWith("#| ") -> previous += line.substringAfter("#| ")
                        line.startsWith("msgctxt ") -> context = line.substringAfter("msgctxt ").unescape()
                        line.startsWith("msgid ") -> text = line.substringAfter("msgid ").unescape()
                        line.startsWith("msgid_plural ") -> plural = line.substringAfter("msgid_plural ").unescape()
                        line.startsWith("msgstr ") -> cases += line.substringAfter("msgstr ").unescape()
                        line.startsWith("msgstr[") -> cases += line.substringAfter("msgstr[").substringAfter("] ").unescape()
                        else -> {
                            if (cases.isNotEmpty()) {
                                cases[0] = cases[0] + "\"\n\"" + line.trim('"')
                            }
                        }
                    }
                }
                text?.let {
                    entries += PoEntry(comments, extractedComments, references, flags, previous, context, it, plural, cases)
                }
            }

            return PoFile(entries.filter { it.text.isNotEmpty() }, entries.firstOrNull { it.text.isEmpty() } ?: DEFAULT_POT_HEADER)
        }

        private fun String.unescape(): String {
            val trimmed = trim()
            val unquoted = if (trimmed.startsWith('"')) trimmed.substring(1, trimmed.lastIndex) else trimmed
            return unquoted.replace("\\\"", "\"").replace("\\n", "\n")
        }
    }
}

val DEFAULT_POT_HEADER = PoEntry(
    listOf(
        "SOME DESCRIPTIVE TITLE.",
        "Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER",
        "This file is distributed under the same license as the PACKAGE package.",
        "FIRST AUTHOR , YEAR.",
        ""
    ),
    emptyList(),
    emptyList(),
    "fuzzy",
    emptyList(),
    null,
    "",
    null,
    listOf(
        "Project-Id-Version: PACKAGE VERSION\n"+
        "Report-Msgid-Bugs-To: \n"+
        "\n"+
        "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"+
        "Last-Translator: FULL NAME \n"+
        "Language-Team: LANGUAGE \n"+
        "Language: \n"+
        "MIME-Version: 1.0\n"+
        "Content-Type: text/plain; charset=UTF-8\n"+
        "Content-Transfer-Encoding: 8bit\n"+
        "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
    )
)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy