Browse Source

Merge pull request #647 from knuckleswtf/internationalization

Internationalization / Localization
Shalvah 2 years ago
parent
commit
4ddc105bff

+ 34 - 0
lang/en.json

@@ -0,0 +1,34 @@
+{
+  "scribe::auth.instruction.query": "To authenticate requests, include a query parameter **`:parameterName`** in the request.",
+  "scribe::auth.instruction.body": "To authenticate requests, include a parameter **`:parameterName`** in the body of the request.",
+  "scribe::auth.instruction.query_or_body": "To authenticate requests, include a parameter **`:parameterName`** either in the query string or in the request body.",
+  "scribe::auth.instruction.bearer": "To authenticate requests, include an **`Authorization`** header with the value **`\"Bearer :placeholder\"`**.",
+  "scribe::auth.instruction.basic": "To authenticate requests, include an **`Authorization`** header in the form **`\"Basic {credentials}\"`**. The value of `{credentials}` should be your username/id and your password, joined with a colon (:), and then base64-encoded.",
+  "scribe::auth.instruction.header": "To authenticate requests, include a **`:parameterName`** header with the value **`\":placeholder\"`**.",
+  "scribe::auth.details": "All authenticated endpoints are marked with a `requires authentication` badge in the documentation below.",
+  "scribe::search": "Search",
+  "scribe::base_url": "Base URL",
+  "scribe::headers.introduction": "Introduction",
+  "scribe::headers.auth": "Authenticating requests",
+  "scribe::no_auth": "This API is not authenticated.",
+  "scribe::example_request": "Example request",
+  "scribe::example_response": "Example response",
+  "scribe::example_response.binary": "Binary data",
+  "scribe::example_response.empty": "Empty response",
+  "scribe::endpoint.request": "Request",
+  "scribe::endpoint.headers": "Headers",
+  "scribe::endpoint.url_parameters": "URL Parameters",
+  "scribe::endpoint.body_parameters": "Body Parameters",
+  "scribe::endpoint.query_parameters": "Query Parameters",
+  "scribe::endpoint.response": "Response",
+  "scribe::endpoint.response_fields": "Response Fields",
+  "scribe::try_it_out.open": "Try it out ⚡",
+  "scribe::try_it_out.cancel": "Cancel 🛑",
+  "scribe::try_it_out.send": "Send Request 💥",
+  "scribe::try_it_out.loading": "⏱ Sending...",
+  "scribe::try_it_out.received_response": "Received response",
+  "scribe::try_it_out.request_failed": "Request failed with error",
+  "scribe::try_it_out.error_help": "Tip: Check that you're properly connected to the network.\nIf you're a maintainer of ths API, verify that your API is running and you've enabled CORS.\nYou can check the Dev Tools console for debugging information.",
+  "scribe::links.postman": "View Postman collection",
+  "scribe::links.openapi": "View OpenAPI spec"
+}

+ 12 - 11
resources/js/tryitout.js

@@ -38,8 +38,9 @@ function getCookie(name) {
 
 function tryItOut(endpointId) {
     document.querySelector(`#btn-tryout-${endpointId}`).hidden = true;
-    document.querySelector(`#btn-executetryout-${endpointId}`).hidden = false;
     document.querySelector(`#btn-canceltryout-${endpointId}`).hidden = false;
+    const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`).hidden = false;
+    executeBtn.disabled = false;
 
     // Show all input fields
     document.querySelectorAll(`input[data-endpoint=${endpointId}],label[data-endpoint=${endpointId}]`)
@@ -63,7 +64,7 @@ function cancelTryOut(endpointId) {
     document.querySelector(`#btn-tryout-${endpointId}`).hidden = false;
     const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`);
     executeBtn.hidden = true;
-    executeBtn.textContent = "Send Request 💥";
+    executeBtn.textContent = executeBtn.dataset.initialText;
     document.querySelector(`#btn-canceltryout-${endpointId}`).hidden = true;
     // Hide inputs
     document.querySelectorAll(`input[data-endpoint=${endpointId}],label[data-endpoint=${endpointId}]`)
@@ -88,7 +89,7 @@ function makeAPICall(method, path, body = {}, query = {}, headers = {}, endpoint
         body = JSON.stringify(body)
     }
 
-    const url = new URL(window.baseUrl + '/' + path.replace(/^\//, ''));
+    const url = new URL(window.tryItOutBaseUrl + '/' + path.replace(/^\//, ''));
 
     // We need this function because if you try to set an array or object directly to a URLSearchParams object,
     // you'll get [object Object] or the array.toString()
@@ -118,7 +119,7 @@ function makeAPICall(method, path, body = {}, query = {}, headers = {}, endpoint
         headers,
         body: method === 'GET' ? undefined : body,
         signal: window.abortControllers[endpointId].signal,
-        referrer: window.baseUrl,
+        referrer: window.tryItOutBaseUrl,
         mode: 'cors',
         credentials: 'same-origin',
     })
@@ -149,7 +150,7 @@ function handleResponse(endpointId, response, status, headers) {
     } catch (e) {
 
     }
-    responseContentEl.textContent = response === '' ? '<Empty response>' : response;
+    responseContentEl.textContent = response === '' ? responseContentEl.dataset.emptyResponseText : response;
     isJson && window.hljs.highlightElement(responseContentEl);
     const statusEl = document.querySelector('#execution-response-status-' + endpointId);
     statusEl.textContent = ` (${status})`;
@@ -164,10 +165,8 @@ function handleError(endpointId, err) {
 
     // Show error views
     let errorMessage = err.message || err;
-    errorMessage += "\n\nTip: Check that you're properly connected to the network.";
-    errorMessage += "\nIf you're a maintainer of ths API, verify that your API is running and you've enabled CORS.";
-    errorMessage += "\nYou can check the Dev Tools console for debugging information.";
-    document.querySelector('#execution-error-message-' + endpointId).textContent = errorMessage;
+    const $errorMessageEl = document.querySelector('#execution-error-message-' + endpointId);
+    $errorMessageEl.textContent = errorMessage + $errorMessageEl.textContent;
     const errorEl = document.querySelector('#execution-error-' + endpointId);
     errorEl.hidden = false;
     errorEl.scrollIntoView({behavior: "smooth", block: "center"});
@@ -176,7 +175,8 @@ function handleError(endpointId, err) {
 
 async function executeTryOut(endpointId, form) {
     const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`);
-    executeBtn.textContent = "⏱ Sending...";
+    executeBtn.textContent = executeBtn.dataset.loadingText;
+    executeBtn.disabled = true;
     executeBtn.scrollIntoView({behavior: "smooth", block: "center"});
 
     let body;
@@ -266,6 +266,7 @@ async function executeTryOut(endpointId, form) {
             handleError(endpointId, err);
         })
         .finally(() => {
-            executeBtn.textContent = "Send Request 💥";
+            executeBtn.disabled = false;
+            executeBtn.textContent = executeBtn.dataset.initialText;
         });
 }

+ 2 - 2
resources/views/markdown/auth.blade.php

@@ -1,7 +1,7 @@
-# Authenticating requests
+# {{ __("scribe::headers.auth") }}
 
 @if(!$isAuthed)
-This API is not authenticated.
+{!! __("scribe::no_auth") !!}
 @else
 {!! $authDescription !!}
 

+ 2 - 2
resources/views/markdown/intro.blade.php

@@ -1,9 +1,9 @@
-# Introduction
+# {{ __("scribe::headers.introduction") }}
 
 {!! $description !!}
 
 <aside>
-    <strong>Base URL</strong>: <code>{!! $baseUrl !!}</code>
+    <strong>{{ __("scribe::base_url") }}</strong>: <code>{!! $baseUrl !!}</code>
 </aside>
 
 {!! $introText !!}

+ 22 - 18
resources/views/themes/default/endpoint.blade.php

@@ -12,7 +12,7 @@
 {!! Parsedown::instance()->text($endpoint->metadata->description ?: '') !!}
 
 <span id="example-requests-{!! $endpoint->endpointId() !!}">
-<blockquote>Example request:</blockquote>
+<blockquote>{{ __("scribe::example_request") }}:</blockquote>
 
 @foreach($metadata['example_languages'] as $language)
 
@@ -27,7 +27,7 @@
 @if($endpoint->isGet() || $endpoint->hasResponses())
     @foreach($endpoint->responses as $response)
         <blockquote>
-            <p>Example response ({{ $response->fullDescription() }}):</p>
+            <p>{{ __("scribe::example_response") }} ({{ $response->fullDescription() }}):</p>
         </blockquote>
         @if(count($response->headers))
         <details class="annotation">
@@ -39,9 +39,9 @@
 @endforeach </code></pre></details> @endif
         <pre>
 @if(is_string($response->content) && Str::startsWith($response->content, "<<binary>>"))
-<code>[Binary data] - {{ htmlentities(str_replace("<<binary>>", "", $response->content)) }}</code>
+<code>{!! __("scribe::example_response.binary") !!} - {{ htmlentities(str_replace("<<binary>>", "", $response->content)) }}</code>
 @elseif($response->status == 204)
-<code>[Empty response]</code>
+<code>{!! __("scribe::example_response.empty") !!}</code>
 @else
 @php($parsed = json_decode($response->content))
 {{-- If response is a JSON string, prettify it. Otherwise, just print it --}}
@@ -51,14 +51,15 @@
 @endif
 </span>
 <span id="execution-results-{{ $endpoint->endpointId() }}" hidden>
-    <blockquote>Received response<span
+    <blockquote>{{ __("scribe::try_it_out.received_response") }}<span
                 id="execution-response-status-{{ $endpoint->endpointId() }}"></span>:
     </blockquote>
-    <pre class="json"><code id="execution-response-content-{{ $endpoint->endpointId() }}" style="max-height: 400px;"></code></pre>
+    <pre class="json"><code id="execution-response-content-{{ $endpoint->endpointId() }}"
+      data-empty-response-text="<{{ __("scribe::example_response.empty") }}>" style="max-height: 400px;"></code></pre>
 </span>
 <span id="execution-error-{{ $endpoint->endpointId() }}" hidden>
-    <blockquote>Request failed with error:</blockquote>
-    <pre><code id="execution-error-message-{{ $endpoint->endpointId() }}"></code></pre>
+    <blockquote>{{ __("scribe::try_it_out.request_failed") }}:</blockquote>
+    <pre><code id="execution-error-message-{{ $endpoint->endpointId() }}">{{ "\n\n".__("scribe::try_it_out.error_help") }}</code></pre>
 </span>
 <form id="form-{{ $endpoint->endpointId() }}" data-method="{{ $endpoint->httpMethods[0] }}"
       data-path="{{ $endpoint->uri }}"
@@ -68,21 +69,24 @@
       autocomplete="off"
       onsubmit="event.preventDefault(); executeTryOut('{{ $endpoint->endpointId() }}', this);">
     <h3>
-        Request&nbsp;&nbsp;&nbsp;
+        {{ __("scribe::endpoint.request") }}&nbsp;&nbsp;&nbsp;
         @if($metadata['try_it_out']['enabled'] ?? false)
             <button type="button"
                     style="background-color: #8fbcd4; padding: 5px 10px; border-radius: 5px; border-width: thin;"
                     id="btn-tryout-{{ $endpoint->endpointId() }}"
-                    onclick="tryItOut('{{ $endpoint->endpointId() }}');">Try it out ⚡
+                    onclick="tryItOut('{{ $endpoint->endpointId() }}');">{{ __("scribe::try_it_out.open") }}
             </button>
             <button type="button"
                     style="background-color: #c97a7e; padding: 5px 10px; border-radius: 5px; border-width: thin;"
                     id="btn-canceltryout-{{ $endpoint->endpointId() }}"
-                    onclick="cancelTryOut('{{ $endpoint->endpointId() }}');" hidden>Cancel 🛑
+                    onclick="cancelTryOut('{{ $endpoint->endpointId() }}');" hidden>{{ __("scribe::try_it_out.cancel") }}
             </button>&nbsp;&nbsp;
             <button type="submit"
                     style="background-color: #6ac174; padding: 5px 10px; border-radius: 5px; border-width: thin;"
-                    id="btn-executetryout-{{ $endpoint->endpointId() }}" hidden>Send Request 💥
+                    id="btn-executetryout-{{ $endpoint->endpointId() }}"
+                    data-initial-text="{{ __("scribe::try_it_out.send") }}"
+                    data-loading-text="{{ __("scribe::try_it_out.loading") }}"
+                    hidden>{{ __("scribe::try_it_out.send") }}
             </button>
         @endif
     </h3>
@@ -93,7 +97,7 @@
         </p>
     @endforeach
     @if(count($endpoint->headers))
-        <h4 class="fancy-heading-panel"><b>Headers</b></h4>
+        <h4 class="fancy-heading-panel"><b>{{ __("scribe::endpoint.headers") }}</b></h4>
         @foreach($endpoint->headers as $name => $example)
             <?php
                 $htmlOptions = [];
@@ -118,7 +122,7 @@
         @endforeach
     @endif
     @if(count($endpoint->urlParameters))
-        <h4 class="fancy-heading-panel"><b>URL Parameters</b></h4>
+        <h4 class="fancy-heading-panel"><b>{{ __("scribe::endpoint.url_parameters") }}</b></h4>
         @foreach($endpoint->urlParameters as $attribute => $parameter)
             <div style="padding-left: 28px; clear: unset;">
                 @component('scribe::components.field-details', [
@@ -136,7 +140,7 @@
         @endforeach
     @endif
     @if(count($endpoint->queryParameters))
-        <h4 class="fancy-heading-panel"><b>Query Parameters</b></h4>
+        <h4 class="fancy-heading-panel"><b>{{ __("scribe::endpoint.query_parameters") }}</b></h4>
         @foreach($endpoint->queryParameters as $attribute => $parameter)
                 <?php
                 $htmlOptions = [];
@@ -161,7 +165,7 @@
         @endforeach
     @endif
     @if(count($endpoint->nestedBodyParameters))
-        <h4 class="fancy-heading-panel"><b>Body Parameters</b></h4>
+        <h4 class="fancy-heading-panel"><b>{{ __("scribe::endpoint.body_parameters") }}</b></h4>
         <x-scribe::nested-fields
                 :fields="$endpoint->nestedBodyParameters" :endpointId="$endpoint->endpointId()"
         />
@@ -169,8 +173,8 @@
 </form>
 
 @if(count($endpoint->responseFields))
-    <h3>Response</h3>
-    <h4 class="fancy-heading-panel"><b>Response Fields</b></h4>
+    <h3>{{ __("scribe::endpoint.response") }}</h3>
+    <h4 class="fancy-heading-panel"><b>{{ __("scribe::endpoint.response_fields") }}</b></h4>
     <x-scribe::nested-fields
             :fields="$endpoint->nestedResponseFields" :endpointId="$endpoint->endpointId()"
             :isInput="false"

+ 1 - 1
resources/views/themes/default/index.blade.php

@@ -33,7 +33,7 @@
 
 @if($tryItOut['enabled'] ?? true)
     <script>
-        var baseUrl = "{{ $tryItOut['base_url'] ?? config('app.url') }}";
+        var tryItOutBaseUrl = "{{ $tryItOut['base_url'] ?? config('app.url') }}";
         var useCsrf = Boolean({{ $tryItOut['use_csrf'] ?? null }});
         var csrfUrl = "{{ $tryItOut['csrf_url'] ?? null }}";
     </script>

+ 3 - 3
resources/views/themes/default/sidebar.blade.php

@@ -19,7 +19,7 @@
     @endisset
 
     <div class="search">
-        <input type="text" class="search" id="input-search" placeholder="Search">
+        <input type="text" class="search" id="input-search" placeholder="{{ __("scribe::search") }}">
     </div>
 
     <div id="toc">
@@ -52,10 +52,10 @@
 
     <ul class="toc-footer" id="toc-footer">
         @if($metadata['postman_collection_url'])
-            <li style="padding-bottom: 5px;"><a href="{!! $metadata['postman_collection_url'] !!}">View Postman collection</a></li>
+            <li style="padding-bottom: 5px;"><a href="{!! $metadata['postman_collection_url'] !!}">{!! __("scribe::links.postman") !!}</a></li>
         @endif
         @if($metadata['openapi_spec_url'])
-            <li style="padding-bottom: 5px;"><a href="{!! $metadata['openapi_spec_url'] !!}">View OpenAPI spec</a></li>
+            <li style="padding-bottom: 5px;"><a href="{!! $metadata['openapi_spec_url'] !!}">{!! __("scribe::links.openapi") !!}</a></li>
         @endif
         <li><a href="http://github.com/knuckleswtf/scribe">Documentation powered by Scribe ✍</a></li>
     </ul>

+ 9 - 9
resources/views/themes/elements/endpoint.blade.php

@@ -48,7 +48,7 @@
                     @if(count($endpoint->headers))
                         <div class="sl-stack sl-stack--vertical sl-stack--5 sl-flex sl-flex-col sl-items-stretch">
                             <h3 class="sl-text-2xl sl-leading-snug sl-font-prose">
-                                Headers
+                                {{ __("scribe::endpoint.headers") }}
                             </h3>
                             <div class="sl-text-sm">
                                 @foreach($endpoint->headers as $header => $value)
@@ -70,7 +70,7 @@
 
                     @if(count($endpoint->urlParameters))
                         <div class="sl-stack sl-stack--vertical sl-stack--6 sl-flex sl-flex-col sl-items-stretch">
-                            <h3 class="sl-text-2xl sl-leading-snug sl-font-prose">URL Parameters</h3>
+                            <h3 class="sl-text-2xl sl-leading-snug sl-font-prose">{{ __("scribe::endpoint.url_parameters") }}</h3>
 
                             <div class="sl-text-sm">
                                 @foreach($endpoint->urlParameters as $attribute => $parameter)
@@ -93,7 +93,7 @@
 
                     @if(count($endpoint->queryParameters))
                             <div class="sl-stack sl-stack--vertical sl-stack--6 sl-flex sl-flex-col sl-items-stretch">
-                                <h3 class="sl-text-2xl sl-leading-snug sl-font-prose">Query Parameters</h3>
+                                <h3 class="sl-text-2xl sl-leading-snug sl-font-prose">{{ __("scribe::endpoint.query_parameters") }}</h3>
 
                                 <div class="sl-text-sm">
                                     @foreach($endpoint->queryParameters as $attribute => $parameter)
@@ -115,7 +115,7 @@
 
                     @if(count($endpoint->nestedBodyParameters))
                         <div class="sl-stack sl-stack--vertical sl-stack--6 sl-flex sl-flex-col sl-items-stretch">
-                            <h3 class="sl-text-2xl sl-leading-snug sl-font-prose">Body Parameters</h3>
+                            <h3 class="sl-text-2xl sl-leading-snug sl-font-prose">{{ __("scribe::endpoint.body_parameters") }}</h3>
 
                                 <div class="sl-text-sm">
                                     @component('scribe::themes.elements.components.nested-fields', [
@@ -129,7 +129,7 @@
 
                     @if(count($endpoint->responseFields))
                             <div class="sl-stack sl-stack--vertical sl-stack--6 sl-flex sl-flex-col sl-items-stretch">
-                                <h3 class="sl-text-2xl sl-leading-snug sl-font-prose">Response Fields</h3>
+                                <h3 class="sl-text-2xl sl-leading-snug sl-font-prose">{{ __("scribe::endpoint.response_fields") }}</h3>
 
                                 <div class="sl-text-sm">
                                     @component('scribe::themes.elements.components.nested-fields', [
@@ -157,7 +157,7 @@
                             <div class="sl-panel__titlebar sl-flex sl-items-center sl-relative focus:sl-z-10 sl-text-base sl-leading-none sl-pr-3 sl-pl-4 sl-bg-canvas-200 sl-text-body sl-border-input focus:sl-border-primary sl-select-none">
                                 <div class="sl-flex sl-flex-1 sl-items-center sl-h-lg">
                                     <div class="sl--ml-2">
-                                        Request sample:
+                                        {{ __("scribe::example_request") }}:
                                         <select class="example-request-lang-toggle sl-text-base"
                                                 aria-label="Request Sample Language"
                                                 onchange="switchExampleLanguage(event.target.value);">
@@ -187,7 +187,7 @@
                                 <div class="sl-flex sl-flex-1 sl-items-center sl-py-2">
                                     <div class="sl--ml-2">
                                         <div class="sl-h-sm sl-text-base sl-font-medium sl-px-1.5 sl-text-muted sl-rounded sl-border-transparent sl-border">
-                                            <div class="sl-mb-2 sl-inline-block">Response sample:</div>
+                                            <div class="sl-mb-2 sl-inline-block">{{ __("scribe::example_response") }}:</div>
                                             <div class="sl-mb-2 sl-inline-block">
                                                 <select
                                                         class="example-response-{{ $endpoint->endpointId() }}-toggle sl-text-base"
@@ -240,9 +240,9 @@
                                             </details>
                                         @endif
                                         @if(is_string($response->content) && Str::startsWith($response->content, "<<binary>>"))
-                                            <pre><code>[Binary data] - {{ htmlentities(str_replace("<<binary>>", "", $response->content)) }}</code></pre>
+                                            <pre><code>[{{ __("scribe::example_response.binary") }}] - {{ htmlentities(str_replace("<<binary>>", "", $response->content)) }}</code></pre>
                                         @elseif($response->status == 204)
-                                            <pre><code>[Empty response]</code></pre>
+                                            <pre><code>[{{ __("scribe::example_response.empty") }}]</code></pre>
                                         @else
                                             @php($parsed = json_decode($response->content))
                                             {{-- If response is a JSON string, prettify it. Otherwise, just print it --}}

+ 4 - 4
resources/views/themes/elements/index.blade.php

@@ -27,7 +27,7 @@
 
     @if($tryItOut['enabled'] ?? true)
         <script>
-            var baseUrl = "{{ $tryItOut['base_url'] ?? config('app.url') }}";
+            var tryItOutBaseUrl = "{{ $tryItOut['base_url'] ?? config('app.url') }}";
             var useCsrf = Boolean({{ $tryItOut['use_csrf'] ?? null }});
             var csrfUrl = "{{ $tryItOut['csrf_url'] ?? null }}";
         </script>
@@ -75,9 +75,9 @@
                 responsePanel.hidden = true;
 
                 let form = btnElement.form;
-                let { method, path, hasjsonbody } = form.dataset;
+                let { method, path, hasjsonbody: hasJsonBody} = form.dataset;
                 let body = {};
-                if (hasjsonbody === "1") {
+                if (hasJsonBody === "1") {
                     body = form.querySelector('.code-editor').textContent;
                 } else if (form.dataset.hasfiles === "1") {
                     body = new FormData();
@@ -118,7 +118,7 @@
 
                         let contentEl = responsePanel.querySelector(`.response-content`);
                         if (responseContent === '') {
-                            contentEl.textContent = '<Empty response>'
+                            contentEl.textContent = contentEl.dataset.emptyResponseText;
                             return;
                         }
 

+ 8 - 16
resources/views/themes/elements/try_it_out.blade.php

@@ -260,7 +260,7 @@
                     <button type="button" data-endpoint="{{ $endpoint->endpointId() }}"
                             class="tryItOut-btn sl-button sl-h-sm sl-text-base sl-font-medium sl-px-1.5 sl-bg-primary hover:sl-bg-primary-dark active:sl-bg-primary-darker disabled:sl-bg-canvas-100 sl-text-on-primary disabled:sl-text-body sl-rounded sl-border-transparent sl-border disabled:sl-opacity-70"
                     >
-                        Send API Request
+                        {{ __("scribe::try_it_out.send") }}
                     </button>
                 </div>
             </div>
@@ -279,23 +279,13 @@
                                       d="M310.6 246.6l-127.1 128C176.4 380.9 168.2 384 160 384s-16.38-3.125-22.63-9.375l-127.1-128C.2244 237.5-2.516 223.7 2.438 211.8S19.07 192 32 192h255.1c12.94 0 24.62 7.781 29.58 19.75S319.8 237.5 310.6 246.6z"></path>
                             </svg>
                         </div>
-                        Error
+                        {{ __("scribe::try_it_out.request_failed") }}
                     </div>
                 </div>
                 <div class="sl-panel__content-wrapper sl-bg-canvas-100 children" role="region">
                     <div class="sl-panel__content sl-p-4">
-                        <p class="sl-pb-2"><strong
-                                    class="error-message"></strong></p>
-                        <p class="sl-pb-2">1. Double check that your computer is connected to
-                            the internet.</p>
-                        <p class="sl-pb-2">2. Make sure the API is actually running and
-                            available under the specified URL.</p>
-                        <p>3. If you've checked all of the above and still experiencing issues,
-                            check if the API supports <a target="_blank"
-                                                         rel="noopener noreferrer"
-                                                         href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS"
-                                                         class="sl-link sl-font-semibold">CORS</a>.
-                        </p>
+                        <p class="sl-pb-2"><strong class="error-message"></strong></p>
+                        <p class="sl-pb-2">{{ __("scribe::try_it_out.error_help") }}</p>
                     </div>
                 </div>
             </div>
@@ -314,13 +304,15 @@
                                           d="M310.6 246.6l-127.1 128C176.4 380.9 168.2 384 160 384s-16.38-3.125-22.63-9.375l-127.1-128C.2244 237.5-2.516 223.7 2.438 211.8S19.07 192 32 192h255.1c12.94 0 24.62 7.781 29.58 19.75S319.8 237.5 310.6 246.6z"></path>
                                 </svg>
                             </div>
-                            Response
+                            {{ __("scribe::try_it_out.received_response") }}
                         </div>
                     </div>
                     <div class="sl-panel__content-wrapper sl-bg-canvas-100 children" role="region">
                         <div class="sl-panel__content sl-p-4">
                             <p class="sl-pb-2 response-status"></p>
-                            <pre><code class="sl-pb-2 response-content language-json" style="max-height: 300px;"></code></pre>
+                            <pre><code class="sl-pb-2 response-content language-json"
+                                       data-empty-response-text="<{{ __("scribe::example_response.empty") }}>"
+                                       style="max-height: 300px;"></code></pre>
                         </div>
                     </div>
                 </div>

+ 12 - 18
src/Extracting/ApiDetails.php

@@ -2,7 +2,7 @@
 
 namespace Knuckles\Scribe\Extracting;
 
-use Knuckles\Scribe\Tools\ConsoleOutputUtils;
+use Knuckles\Scribe\Tools\ConsoleOutputUtils as c;
 use Knuckles\Scribe\Tools\DocumentationConfig;
 
 /**
@@ -36,7 +36,7 @@ class ApiDetails
 
     public function writeMarkdownFiles(): void
     {
-        ConsoleOutputUtils::info('Extracting intro and auth Markdown files to: ' . $this->markdownOutputPath);
+        c::info('Extracting intro and auth Markdown files to: ' . $this->markdownOutputPath);
 
         if (!is_dir($this->markdownOutputPath)) {
             mkdir($this->markdownOutputPath, 0777, true);
@@ -49,7 +49,7 @@ class ApiDetails
 
         $this->writeContentsTrackingFile();
 
-        ConsoleOutputUtils::success('Extracted intro and auth Markdown files to: ' . $this->markdownOutputPath);
+        c::success('Extracted intro and auth Markdown files to: ' . $this->markdownOutputPath);
     }
 
     public function writeIntroMarkdownFile(): void
@@ -57,11 +57,11 @@ class ApiDetails
         $introMarkdownFile = $this->markdownOutputPath . '/intro.md';
         if ($this->hasFileBeenModified($introMarkdownFile)) {
             if ($this->preserveUserChanges) {
-                ConsoleOutputUtils::warn("Skipping modified file $introMarkdownFile");
+                c::warn("Skipping modified file $introMarkdownFile");
                 return;
             }
 
-            ConsoleOutputUtils::warn("Discarding manual changes for file $introMarkdownFile because you specified --force");
+            c::warn("Discarding manual changes for file $introMarkdownFile because you specified --force");
         }
 
         $introMarkdown = view('scribe::markdown.intro')
@@ -76,11 +76,11 @@ class ApiDetails
         $authMarkdownFile = $this->markdownOutputPath . '/auth.md';
         if ($this->hasFileBeenModified($authMarkdownFile)) {
             if ($this->preserveUserChanges) {
-                ConsoleOutputUtils::warn("Skipping modified file $authMarkdownFile");
+                c::warn("Skipping modified file $authMarkdownFile");
                 return;
             }
 
-            ConsoleOutputUtils::warn("Discarding manual changes for file $authMarkdownFile because you specified --force");
+            c::warn("Discarding manual changes for file $authMarkdownFile because you specified --force");
         }
 
         $isAuthed = $this->config->get('auth.enabled', false);
@@ -90,17 +90,11 @@ class ApiDetails
         if ($isAuthed) {
             $strategy = $this->config->get('auth.in');
             $parameterName = $this->config->get('auth.name');
-            $authDescription = "To authenticate requests, include ";
-            $authDescription .= match ($strategy) {
-                'query' => "a query parameter **`$parameterName`** in the request.",
-                'body' => "a parameter **`$parameterName`** in the body of the request.",
-                'query_or_body' => "a parameter **`$parameterName`** either in the query string or in the request body.",
-                'bearer' => sprintf('an **`Authorization`** header with the value **`"Bearer %s"`**.', $this->config->get('auth.placeholder') ?: 'your-token'),
-                'basic' => "an **`Authorization`** header in the form **`\"Basic {credentials}\"`**. The value of `{credentials}` should be your username/id and your password, joined with a colon (:), and then base64-encoded.",
-                'header' => sprintf('a **`%s`** header with the value **`"%s"`**.', $parameterName, $this->config->get('auth.placeholder') ?: 'your-token'),
-                default => '',
-            };
-            $authDescription .= "\n\nAll authenticated endpoints are marked with a `requires authentication` badge in the documentation below.";
+            $authDescription = __("scribe::auth.instruction.$strategy", [
+                'parameterName' => $parameterName,
+                'placeholder' => $this->config->get('auth.placeholder') ?: 'your-token']
+            );
+            $authDescription .= "\n\n".__("scribe::auth.details");
             $extraInfo = $this->config->get('auth.extra_info', '');
         }
 

+ 5 - 0
src/ScribeServiceProvider.php

@@ -24,6 +24,11 @@ class ScribeServiceProvider extends ServiceProvider
 
         $this->registerCommands();
 
+        $this->loadJsonTranslationsFrom(__DIR__.'/../lang');
+        $this->publishes([
+            __DIR__.'/../lang' => $this->app->langPath('vendor/scribe'),
+        ], 'scribe-translations');
+
         // Bind the route matcher implementation
         $this->app->bind(RouteMatcherInterface::class, config('scribe.routeMatcher', RouteMatcher::class));
 

+ 2 - 2
src/Writing/HtmlWriter.php

@@ -112,11 +112,11 @@ class HtmlWriter
 
         // NB:These paths are wrong for laravel type but will be set correctly by the Writer class
         if ($this->config->get('postman.enabled', true)) {
-            $links[] = "<a href=\"{$this->assetPathPrefix}collection.json\">View Postman collection</a>";
+            $links[] = "<a href=\"{$this->assetPathPrefix}collection.json\">".__("scribe::links.postman")."</a>";
             $postmanCollectionUrl = "{$this->assetPathPrefix}collection.json";
         }
         if ($this->config->get('openapi.enabled', false)) {
-            $links[] = "<a href=\"{$this->assetPathPrefix}openapi.yaml\">View OpenAPI spec</a>";
+            $links[] = "<a href=\"{$this->assetPathPrefix}openapi.yaml\">".__("scribe::links.openapi")."</a>";
             $openApiSpecUrl = "{$this->assetPathPrefix}openapi.yaml";
         }
 

+ 1 - 6
tests/Strategies/Responses/UseApiResourceTagsTest.php

@@ -23,12 +23,7 @@ class UseApiResourceTagsTest extends BaseLaravelTest
 
     protected function getPackageProviders($app)
     {
-        $providers = [
-            ScribeServiceProvider::class,
-        ];
-        if (class_exists(\Dingo\Api\Provider\LaravelServiceProvider::class)) {
-            $providers[] = \Dingo\Api\Provider\LaravelServiceProvider::class;
-        }
+        $providers = parent::getPackageProviders($app);
         if (class_exists(\Illuminate\Database\Eloquent\LegacyFactoryServiceProvider::class)) {
             $providers[] = \Illuminate\Database\Eloquent\LegacyFactoryServiceProvider ::class;
         }

+ 1 - 3
tests/Strategies/Responses/UseResponseAttributesTest.php

@@ -28,9 +28,7 @@ class UseResponseAttributesTest extends BaseLaravelTest
 
     protected function getPackageProviders($app)
     {
-        $providers = [
-            ScribeServiceProvider::class,
-        ];
+        $providers = parent::getPackageProviders($app);
         if (class_exists(\Illuminate\Database\Eloquent\LegacyFactoryServiceProvider::class)) {
             $providers[] = \Illuminate\Database\Eloquent\LegacyFactoryServiceProvider ::class;
         }

+ 2 - 2
tests/Unit/HtmlWriterTest.php

@@ -2,11 +2,11 @@
 
 namespace Knuckles\Scribe\Tests\Unit;
 
+use Knuckles\Scribe\Tests\BaseLaravelTest;
 use Knuckles\Scribe\Tools\DocumentationConfig;
 use Knuckles\Scribe\Writing\HtmlWriter;
-use PHPUnit\Framework\TestCase;
 
-class HtmlWriterTest extends TestCase
+class HtmlWriterTest extends BaseLaravelTest
 {
     /** @test */
     public function sets_last_updated_correctly()