diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3130b26 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +[*.{kt,kts}] + +charset=utf-8 + +indent_size=unset +disabled_rules=no-wildcard-imports,import-ordering diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d50f83d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.gradle + +build + +out + +.idea diff --git a/README.md b/README.md index b1b7161..8245bfd 100644 --- a/README.md +++ b/README.md @@ -1 +1,31 @@ -init +# Simple Kotlin https://localise.biz/ API client +---- +All Translations are in Properties format and **load once** + +## What does it look like? (Code snippets) +#### Response from API +```json +{ + "en": { + "hello-world": "Hello", + "world": "World", + "my": { + "another": "Another" + } + } +} +``` +#### "en" in Map +```properties +hello-world = Hello +world = World +my.another = Another +``` + +## How to use? (Code snippets) +```kotlin +val client = LocoClient("") +val i18n = client.translations("en") +println(i18n.t("my.another")) +``` + diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..8eeb71f --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,106 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +group = "com.npwork" +version = "1.0.0-SNAPSHOT" + +plugins { + id("org.jetbrains.kotlin.jvm") version "1.3.41" + + id("org.jlleitschuh.gradle.ktlint") version "9.1.1" + id("io.gitlab.arturbosch.detekt") version "1.4.0" + id("info.solidsoft.pitest") version "1.4.6" + id("com.star-zero.gradle.githook") version "1.2.0" + + jacoco +} + +val mainClass: String by project + +val tornadoFxVersion = "1.7.19" +val junitVersion = "5.5.2" + +repositories { + jcenter() + mavenCentral() + maven(url = "https://jitpack.io") + maven(url = "https://plugins.gradle.org/m2") +} + +dependencies { + // Kotlin + implementation(platform("org.jetbrains.kotlin:kotlin-bom")) + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + + implementation("com.github.wnameless:json-flattener:0.2.2") + implementation("khttp:khttp:1.0.0") + implementation("com.google.code.gson:gson:2.8.6") + implementation("com.github.mmazi:rescu:1.6.0") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.10.+") + implementation("com.google.guava:guava:20.0") + implementation("ch.qos.logback:logback-classic:1.2.3") + + // Test dependencies + testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion") + testImplementation("org.assertj:assertj-core:3.14.0") +} + +detekt { + failFast = true + buildUponDefaultConfig = true + config = files("gradle/detekt/detekt.yml") + reports { + html.enabled = true + xml.enabled = false + txt.enabled = false + } +} + +tasks.test { + useJUnitPlatform() + failFast = true + testLogging { + events("passed", "skipped", "failed") + } + + configure { + isEnabled = true + } +} + +tasks.withType().configureEach { + kotlinOptions.jvmTarget = "1.8" + kotlinOptions.allWarningsAsErrors = true +} + +tasks.wrapper { + gradleVersion = "6.1" +} + +jacoco { + toolVersion = "0.8.5" +} + +pitest { + targetClasses.add("com.cardiolyse.holter.*") + outputFormats.add("HTML") +} + +githook { + createHooksDirIfNotExist = true + hooks { + create("pre-commit") { + task = "build -x test" + shell = "echo 'Build successful'" + } + } +} + +tasks.jacocoTestReport { + executionData.setFrom(fileTree(buildDir).include("/jacoco/*.exec")) + + reports { + xml.isEnabled = false + csv.isEnabled = false + html.isEnabled = true + } +} diff --git a/gradle/detekt/detekt.yml b/gradle/detekt/detekt.yml new file mode 100644 index 0000000..b977726 --- /dev/null +++ b/gradle/detekt/detekt.yml @@ -0,0 +1,604 @@ +build: + maxIssues: 0 + excludeCorrectable: false + weights: + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 + +config: + validation: true + # when writing own rules with new properties, exclude the property path e.g.: "my_rule_set,.*>.*>[my_property]" + excludes: "" + +processors: + active: true + exclude: + # - 'DetektProgressListener' + # - 'FunctionCountProcessor' + # - 'PropertyCountProcessor' + # - 'ClassCountProcessor' + # - 'PackageCountProcessor' + # - 'KtFileCountProcessor' + +console-reports: + active: true + exclude: + # - 'ProjectStatisticsReport' + # - 'ComplexityReport' + # - 'NotificationReport' + # - 'FindingsReport' + - 'FileBasedFindingsReport' + # - 'BuildFailureReport' + +comments: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + CommentOverPrivateFunction: + active: false + CommentOverPrivateProperty: + active: false + EndOfSentenceFormat: + active: false + endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!:]$) + UndocumentedPublicClass: + active: false + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + UndocumentedPublicFunction: + active: false + UndocumentedPublicProperty: + active: false + +complexity: + active: true + ComplexCondition: + active: true + threshold: 4 + ComplexInterface: + active: false + threshold: 10 + includeStaticDeclarations: false + ComplexMethod: + active: true + threshold: 15 + ignoreSingleWhenExpression: false + ignoreSimpleWhenEntries: false + ignoreNestingFunctions: false + nestingFunctions: run,let,apply,with,also,use,forEach,isNotNull,ifNull + LabeledExpression: + active: false + ignoredLabels: "" + LargeClass: + active: true + threshold: 600 + LongMethod: + active: true + threshold: 60 + LongParameterList: + active: true + threshold: 6 + ignoreDefaultParameters: true + MethodOverloading: + active: false + threshold: 6 + NestedBlockDepth: + active: true + threshold: 4 + StringLiteralDuplication: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + threshold: 3 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + TooManyFunctions: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + thresholdInFiles: 11 + thresholdInClasses: 11 + thresholdInInterfaces: 11 + thresholdInObjects: 11 + thresholdInEnums: 11 + ignoreDeprecated: false + ignorePrivate: false + ignoreOverridden: false + +coroutines: + active: true + GlobalCoroutineUsage: + active: false + RedundantSuspendModifier: + active: false + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: "^(_|(ignore|expected).*)" + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: true + ignoreOverridden: false + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKtFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ExceptionRaisedInUnexpectedLocation: + active: false + methodNames: 'toString,hashCode,equals,finalize' + InstanceOfCheckForException: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + NotImplementedDeclaration: + active: false + PrintStackTrace: + active: false + RethrowCaughtException: + active: false + ReturnFromFinally: + active: false + ignoreLabeled: false + SwallowedException: + active: false + ignoredExceptionTypes: 'InterruptedException,NumberFormatException,ParseException,MalformedURLException' + allowedExceptionNameRegex: "^(_|(ignore|expected).*)" + ThrowingExceptionFromFinally: + active: false + ThrowingExceptionInMain: + active: false + ThrowingExceptionsWithoutMessageOrCause: + active: false + exceptions: 'IllegalArgumentException,IllegalStateException,IOException' + ThrowingNewInstanceOfSameException: + active: false + TooGenericExceptionCaught: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + exceptionNames: + - ArrayIndexOutOfBoundsException + - Error + - Exception + - IllegalMonitorStateException + - NullPointerException + - IndexOutOfBoundsException + - RuntimeException + - Throwable + allowedExceptionNameRegex: "^(_|(ignore|expected).*)" + TooGenericExceptionThrown: + active: true + exceptionNames: + - Error + - Exception + - Throwable + - RuntimeException + +formatting: + active: true + android: false + autoCorrect: true + AnnotationOnSeparateLine: + active: false + autoCorrect: true + ChainWrapping: + active: true + autoCorrect: true + CommentSpacing: + active: true + autoCorrect: true + EnumEntryNameCase: + active: false + autoCorrect: true + Filename: + active: true + FinalNewline: + active: true + autoCorrect: true + ImportOrdering: + active: false + autoCorrect: true + Indentation: + active: false + autoCorrect: true + indentSize: 4 + continuationIndentSize: 4 + MaximumLineLength: + active: true + maxLineLength: 120 + ModifierOrdering: + active: true + autoCorrect: true + MultiLineIfElse: + active: true + autoCorrect: true + NoBlankLineBeforeRbrace: + active: true + autoCorrect: true + NoConsecutiveBlankLines: + active: true + autoCorrect: true + NoEmptyClassBody: + active: true + autoCorrect: true + NoEmptyFirstLineInMethodBlock: + active: false + autoCorrect: true + NoLineBreakAfterElse: + active: true + autoCorrect: true + NoLineBreakBeforeAssignment: + active: true + autoCorrect: true + NoMultipleSpaces: + active: true + autoCorrect: true + NoSemicolons: + active: true + autoCorrect: true + NoTrailingSpaces: + active: true + autoCorrect: true + NoUnitReturn: + active: true + autoCorrect: true + NoUnusedImports: + active: true + autoCorrect: true + NoWildcardImports: + active: false + PackageName: + active: true + autoCorrect: true + ParameterListWrapping: + active: true + autoCorrect: true + indentSize: 4 + SpacingAroundColon: + active: true + autoCorrect: true + SpacingAroundComma: + active: true + autoCorrect: true + SpacingAroundCurly: + active: true + autoCorrect: true + SpacingAroundDot: + active: true + autoCorrect: true + SpacingAroundKeyword: + active: true + autoCorrect: true + SpacingAroundOperators: + active: true + autoCorrect: true + SpacingAroundParens: + active: true + autoCorrect: true + SpacingAroundRangeOperator: + active: true + autoCorrect: true + StringTemplate: + active: true + autoCorrect: true + +naming: + active: true + ClassNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + classPattern: '[A-Z$][a-zA-Z0-9$]*' + ConstructorParameterNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + parameterPattern: '[a-z][A-Za-z0-9]*' + privateParameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + EnumNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*' + ForbiddenClassName: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + forbiddenName: '' + FunctionMaxLength: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + maximumFunctionNameLength: 30 + FunctionMinLength: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$' + excludeClassPattern: '$^' + ignoreOverridden: true + FunctionParameterNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + parameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + InvalidPackageDeclaration: + active: false + rootPackage: '' + MatchingDeclarationName: + active: true + MemberNameEqualsClassName: + active: true + ignoreOverridden: true + ObjectPropertyNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$' + TopLevelPropertyNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' + VariableMaxLength: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + maximumVariableNameLength: 64 + VariableMinLength: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + minimumVariableNameLength: 1 + VariableNaming: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + +performance: + active: true + ArrayPrimitive: + active: true + ForEachOnRange: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + SpreadOperator: + active: true + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + UnnecessaryTemporaryInstantiation: + active: true + +potential-bugs: + active: true + Deprecation: + active: false + DuplicateCaseInWhenExpression: + active: true + EqualsAlwaysReturnsTrueOrFalse: + active: true + EqualsWithHashCodeExist: + active: true + ExplicitGarbageCollectionCall: + active: true + HasPlatformType: + active: false + ImplicitDefaultLocale: + active: false + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true + IteratorNotThrowingNoSuchElementException: + active: true + LateinitUsage: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludeAnnotatedProperties: "" + ignoreOnClassesPattern: "" + MapGetWithNotNullAssertionOperator: + active: false + MissingWhenCase: + active: true + RedundantElseInWhen: + active: true + UnconditionalJumpStatementInLoop: + active: false + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: true + UnsafeCast: + active: false + UselessPostfixExpression: + active: false + WrongEqualsTypeParameter: + active: true + +style: + active: true + CollapsibleIfStatements: + active: false + DataClassContainsFunctions: + active: false + conversionFunctionPrefix: 'to' + DataClassShouldBeImmutable: + active: false + EqualsNullCall: + active: true + EqualsOnSignatureLine: + active: false + ExplicitItLambdaParameter: + active: false + ExpressionBodySyntax: + active: false + includeLineWrapping: false + ForbiddenComment: + active: false + values: 'TODO:,FIXME:,STOPSHIP:' + allowedPatterns: "" + ForbiddenImport: + active: false + imports: '' + forbiddenPatterns: "" + ForbiddenMethodCall: + active: false + methods: '' + ForbiddenPublicDataClass: + active: false + ignorePackages: '*.internal,*.internal.*' + ForbiddenVoid: + active: false + ignoreOverridden: false + ignoreUsageInGenerics: false + FunctionOnlyReturningConstant: + active: true + ignoreOverridableFunction: true + excludedFunctions: 'describeContents' + excludeAnnotatedFunction: "dagger.Provides" + LibraryCodeMustSpecifyReturnType: + active: true + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 1 + MagicNumber: + active: true + excludes: "**/*Stylesheet.kt,**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + ignoreNumbers: '-1,0,1,2' + ignoreHashCodeFunction: true + ignorePropertyDeclaration: false + ignoreLocalVariableDeclaration: false + ignoreConstantDeclaration: true + ignoreCompanionObjectPropertyDeclaration: true + ignoreAnnotation: false + ignoreNamedArgument: true + ignoreEnums: false + ignoreRanges: false + MandatoryBracesIfStatements: + active: false + MaxLineLength: + active: true + maxLineLength: 140 + excludePackageStatements: true + excludeImportStatements: true + excludeCommentStatements: false + MayBeConst: + active: true + ModifierOrder: + active: true + NestedClassesVisibility: + active: false + NewLineAtEndOfFile: + active: true + NoTabs: + active: false + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: false + OptionalWhenBraces: + active: false + PreferToOverPairSyntax: + active: false + ProtectedMemberInFinalClass: + active: true + RedundantExplicitType: + active: false + RedundantVisibilityModifierRule: + active: false + ReturnCount: + active: true + max: 3 + excludedFunctions: "equals" + excludeLabeled: false + excludeReturnFromLambda: true + excludeGuardClauses: false + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: false + SpacingBetweenPackageAndImports: + active: false + ThrowsCount: + active: true + max: 2 + TrailingWhitespace: + active: false + UnderscoresInNumericLiterals: + active: false + acceptableDecimalLength: 5 + UnnecessaryAbstractClass: + active: true + excludeAnnotatedClasses: "dagger.Module" + UnnecessaryAnnotationUseSiteTarget: + active: false + UnnecessaryApply: + active: false + UnnecessaryInheritance: + active: true + UnnecessaryLet: + active: false + UnnecessaryParentheses: + active: false + UntilInsteadOfRangeTo: + active: false + UnusedImports: + active: false + UnusedPrivateClass: + active: true + UnusedPrivateMember: + active: false + allowedNames: "(_|ignored|expected|serialVersionUID)" + UseArrayLiteralsInAnnotations: + active: false + UseCheckOrError: + active: false + UseDataClass: + active: false + excludeAnnotatedClasses: "" + allowVars: false + UseIfInsteadOfWhen: + active: false + UseRequire: + active: false + UselessCallOnNotNull: + active: true + UtilityClassWithPublicConstructor: + active: true + VarCouldBeVal: + active: false + WildcardImport: + active: false + excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" + excludeImports: 'java.util.*,kotlinx.android.synthetic.*' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..87b738c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ba94df8 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..af6708f --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..6d57edc --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..ebea055 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "kotlin-localise" diff --git a/src/main/kotlin/com/npwork/localise/LocoClient.kt b/src/main/kotlin/com/npwork/localise/LocoClient.kt new file mode 100644 index 0000000..8c3d2ce --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/LocoClient.kt @@ -0,0 +1,58 @@ +package com.npwork.localise + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.github.wnameless.json.flattener.JsonFlattener +import com.google.common.hash.Hashing +import com.npwork.localise.api.ApiService +import com.npwork.localise.model.assets.Asset +import com.npwork.localise.model.locale.Locale +import com.npwork.localise.model.translations.LangI18NService +import com.npwork.localise.model.translations.TranslationsResponse +import si.mazi.rescu.ClientConfig +import si.mazi.rescu.JacksonConfigureListener +import si.mazi.rescu.RestProxyFactory +import java.nio.charset.StandardCharsets +import javax.ws.rs.HeaderParam + +class LocoClient(val apiKey: String) { + val mapper = ObjectMapper().registerModules(KotlinModule()) + + companion object { + const val URL = "https://localise.biz/api" + const val LOCALE_PATH = "/locales" + const val TRANSLATION_PATH = "/export/all.json" + } + + private val config = ClientConfig().also { + it.add(HeaderParam::class.java, "Authorization", "Loco $apiKey") + it.jacksonConfigureListener = JacksonConfigureListener { objectMapper -> objectMapper!!.registerModules(KotlinModule()) } + } + + private val apiService: ApiService = RestProxyFactory.createProxy(ApiService::class.java, "https://localise.biz", config) + + init { + apiService.authVerify() + } + + fun translations(lang: String): LangI18NService = LangI18NService(allTranslations().all, lang) + + fun allTranslations(): TranslationsResponse { + val resp = apiService.translations() + val responseAsString = mapper.writeValueAsString(resp) + val responseAsJson = mapper.readTree(responseAsString) + + val responseHash = Hashing.sha256() + .hashString(responseAsString, StandardCharsets.UTF_8) + .toString() + + return TranslationsResponse( + responseHash = responseHash, + all = responseAsJson.fields().asSequence().map { it.key to JsonFlattener.flattenAsMap(it.value.toString()) }.toMap() + ) + } + + fun locales(): List = apiService.locales() + + fun assets(): List = apiService.assets() +} diff --git a/src/main/kotlin/com/npwork/localise/api/ApiService.kt b/src/main/kotlin/com/npwork/localise/api/ApiService.kt new file mode 100644 index 0000000..cc9a289 --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/api/ApiService.kt @@ -0,0 +1,26 @@ +package com.npwork.localise.api + +import com.npwork.localise.model.assets.Asset +import com.npwork.localise.model.auth.AuthVerify +import com.npwork.localise.model.locale.Locale +import javax.ws.rs.GET +import javax.ws.rs.Path + +@Path("/api") +interface ApiService { + @GET + @Path("/locales") + fun locales(): List + + @GET + @Path("/auth/verify") + fun authVerify(): AuthVerify + + @GET + @Path("/export/all.json") + fun translations(): Map + + @GET + @Path("/assets") + fun assets(): List +} diff --git a/src/main/kotlin/com/npwork/localise/example/App.kt b/src/main/kotlin/com/npwork/localise/example/App.kt new file mode 100644 index 0000000..5afcf80 --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/example/App.kt @@ -0,0 +1,9 @@ +package com.npwork.localise.example + +import com.npwork.localise.LocoClient + +fun main() { + val client = LocoClient("") + val i18n = client.translations("en") + println(i18n.t("my.another")) +} diff --git a/src/main/kotlin/com/npwork/localise/model/assets/Aliases.kt b/src/main/kotlin/com/npwork/localise/model/assets/Aliases.kt new file mode 100644 index 0000000..980f8bc --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/model/assets/Aliases.kt @@ -0,0 +1,3 @@ +package com.npwork.localise.model.assets + +class Aliases diff --git a/src/main/kotlin/com/npwork/localise/model/assets/Asset.kt b/src/main/kotlin/com/npwork/localise/model/assets/Asset.kt new file mode 100644 index 0000000..d04b267 --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/model/assets/Asset.kt @@ -0,0 +1,13 @@ +package com.npwork.localise.model.assets + +data class Asset( + val aliases: Aliases, + val context: String, + val id: String, + val modified: String, + val notes: String, + val plurals: Int, + val progress: Progress, + val tags: List, + val type: String +) diff --git a/src/main/kotlin/com/npwork/localise/model/assets/Progress.kt b/src/main/kotlin/com/npwork/localise/model/assets/Progress.kt new file mode 100644 index 0000000..9ecc8a9 --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/model/assets/Progress.kt @@ -0,0 +1,7 @@ +package com.npwork.localise.model.assets + +data class Progress( + val flagged: Int, + val translated: Int, + val untranslated: Int +) diff --git a/src/main/kotlin/com/npwork/localise/model/auth/AuthVerify.kt b/src/main/kotlin/com/npwork/localise/model/auth/AuthVerify.kt new file mode 100644 index 0000000..df76413 --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/model/auth/AuthVerify.kt @@ -0,0 +1,7 @@ +package com.npwork.localise.model.auth + +data class AuthVerify( + val user: User, + val project: Project, + val group: Group +) diff --git a/src/main/kotlin/com/npwork/localise/model/auth/Group.kt b/src/main/kotlin/com/npwork/localise/model/auth/Group.kt new file mode 100644 index 0000000..071e381 --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/model/auth/Group.kt @@ -0,0 +1,6 @@ +package com.npwork.localise.model.auth + +data class Group( + val id: Int, + val name: String +) diff --git a/src/main/kotlin/com/npwork/localise/model/auth/Project.kt b/src/main/kotlin/com/npwork/localise/model/auth/Project.kt new file mode 100644 index 0000000..17b586e --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/model/auth/Project.kt @@ -0,0 +1,7 @@ +package com.npwork.localise.model.auth + +data class Project( + val id: Int, + val name: String, + val url: String +) diff --git a/src/main/kotlin/com/npwork/localise/model/auth/User.kt b/src/main/kotlin/com/npwork/localise/model/auth/User.kt new file mode 100644 index 0000000..0c72539 --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/model/auth/User.kt @@ -0,0 +1,7 @@ +package com.npwork.localise.model.auth + +data class User( + val email: String, + val id: Int, + val name: String +) diff --git a/src/main/kotlin/com/npwork/localise/model/locale/GenericResult.kt b/src/main/kotlin/com/npwork/localise/model/locale/GenericResult.kt new file mode 100644 index 0000000..f605335 --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/model/locale/GenericResult.kt @@ -0,0 +1,10 @@ +package com.npwork.localise.model.locale + +import com.fasterxml.jackson.annotation.JsonProperty + +data class GenericResult( + @JsonProperty("result") + val result: ResultType, + @JsonProperty("errors") + val errors: String +) diff --git a/src/main/kotlin/com/npwork/localise/model/locale/Locale.kt b/src/main/kotlin/com/npwork/localise/model/locale/Locale.kt new file mode 100644 index 0000000..3b41cc3 --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/model/locale/Locale.kt @@ -0,0 +1,10 @@ +package com.npwork.localise.model.locale + +data class Locale( + val code: String, + val name: String, + val native: Boolean, + val plurals: Plurals, + val progress: Progress, + val source: Boolean +) diff --git a/src/main/kotlin/com/npwork/localise/model/locale/Plurals.kt b/src/main/kotlin/com/npwork/localise/model/locale/Plurals.kt new file mode 100644 index 0000000..61064ce --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/model/locale/Plurals.kt @@ -0,0 +1,7 @@ +package com.npwork.localise.model.locale + +data class Plurals( + val equation: String, + val forms: List, + val length: Int +) diff --git a/src/main/kotlin/com/npwork/localise/model/locale/Progress.kt b/src/main/kotlin/com/npwork/localise/model/locale/Progress.kt new file mode 100644 index 0000000..662d35d --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/model/locale/Progress.kt @@ -0,0 +1,8 @@ +package com.npwork.localise.model.locale + +data class Progress( + val flagged: Int, + val translated: Int, + val untranslated: Int, + val words: Int +) diff --git a/src/main/kotlin/com/npwork/localise/model/translations/LangI18NService.kt b/src/main/kotlin/com/npwork/localise/model/translations/LangI18NService.kt new file mode 100644 index 0000000..84cff1c --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/model/translations/LangI18NService.kt @@ -0,0 +1,7 @@ +package com.npwork.localise.model.translations + +class LangI18NService(val translations: AllTranslations, val lang: String) { + fun tObject(key: String): Any? = translations[lang]?.get(key) + + fun t(key: String): String? = translations[lang]?.get(key)?.toString() +} diff --git a/src/main/kotlin/com/npwork/localise/model/translations/TranslationsResponse.kt b/src/main/kotlin/com/npwork/localise/model/translations/TranslationsResponse.kt new file mode 100644 index 0000000..449c749 --- /dev/null +++ b/src/main/kotlin/com/npwork/localise/model/translations/TranslationsResponse.kt @@ -0,0 +1,5 @@ +package com.npwork.localise.model.translations + +typealias AllTranslations = Map> + +data class TranslationsResponse(val all: AllTranslations, val responseHash: String) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..761fe65 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + %d{HH:mm:ss.SSS} [%contextName] [%thread] %-5level %logger{36} - %msg %xEx%n + + + + + + + + +